Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fe705a712 | |||
| 3a448e9e17 | |||
| 6e4def4cc5 | |||
| 34b8fd83c1 | |||
| d5774078e0 | |||
| a3623412f9 | |||
| 1ed60537cf | |||
| 037d670fb7 | |||
| 8b817d50c5 | |||
| fa48060a94 | |||
| b41e982daf | |||
| 2fcb449ed8 | |||
| 218bc54a5f | |||
| 64f0333497 | |||
| 8aa7a5780d | |||
| 17c398bcee | |||
| 76f1455372 | |||
| 1bcad6d7cd | |||
| 9a58d9bcb7 | |||
| c2dcac4413 | |||
| 394273e866 | |||
| 1bcc70c174 |
@@ -63,7 +63,7 @@ commands:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
steps:
|
steps:
|
||||||
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings -A clippy::needless_borrow
|
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings
|
||||||
|
|
||||||
run_with_coverage:
|
run_with_coverage:
|
||||||
description: run command with environment for coverage
|
description: run command with environment for coverage
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ RUN apt update &&\
|
|||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
#install and configure rust
|
#install and configure rust
|
||||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-11-27 -y &&\
|
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-01-15 -y &&\
|
||||||
rustup component add rustfmt clippy &&\
|
rustup component add rustfmt clippy &&\
|
||||||
rustup component add rust-std --target wasm32-unknown-unknown
|
rustup component add rust-std --target wasm32-unknown-unknown
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
name: cd
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
-
|
|
||||||
name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
-
|
|
||||||
name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: plumeorg/plume:latest
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
name: cd
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*.*.*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
-
|
|
||||||
name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v3
|
|
||||||
with:
|
|
||||||
images: plumeorg/plume
|
|
||||||
-
|
|
||||||
name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
-
|
|
||||||
name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
+2
-30
@@ -4,26 +4,6 @@
|
|||||||
|
|
||||||
## [Unreleased] - ReleaseDate
|
## [Unreleased] - ReleaseDate
|
||||||
|
|
||||||
## [[0.7.1]] - 2022-01-12
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Introduce environment variable `MAIL_PORT` (#980)
|
|
||||||
- Introduce email sign-up feature (#636, #1002)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Some styling improvements (#976, #977, #978)
|
|
||||||
- Respond with error status code when error (#1002)
|
|
||||||
|
|
||||||
### Fiexed
|
|
||||||
|
|
||||||
- Fix comment link (#974)
|
|
||||||
- Fix a bug that prevents posting articles (#975)
|
|
||||||
- Fix a bug that notification page doesn't show (#981)
|
|
||||||
|
|
||||||
## [[0.7.0]] - 2022-01-02
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Allow `dir` attributes for LtoR text in RtoL document (#860)
|
- Allow `dir` attributes for LtoR text in RtoL document (#860)
|
||||||
@@ -31,18 +11,15 @@
|
|||||||
- Proxy support (#829)
|
- Proxy support (#829)
|
||||||
- Riker a actor system library (#870)
|
- Riker a actor system library (#870)
|
||||||
- (request-target) and Host header in HTTP Signature (#872)
|
- (request-target) and Host header in HTTP Signature (#872)
|
||||||
- Default log levels for RUST_LOG (#885, #886, #919)
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Upgrade some dependent crates (#858)
|
- Upgrade some dependent crates (#858)
|
||||||
- Use tracing crate (#868)
|
- Use tracing crate (#868)
|
||||||
- Update Rust version to nightly-2021-11-27 (#961)
|
- Update Rust version to nightly-2021-01-15 (#878)
|
||||||
- Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878)
|
- Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878)
|
||||||
- Run searcher on actor system (#870)
|
- Run searcher on actor system (#870)
|
||||||
- Extract a function to calculate posts' ap_url and share it with some places (#918)
|
|
||||||
- Use article title as its slug instead of capitalizing and inserting hyphens (#920)
|
- Use article title as its slug instead of capitalizing and inserting hyphens (#920)
|
||||||
- Sign GET requests to other instances (#957)
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
@@ -53,9 +30,6 @@
|
|||||||
- Update post's ActivityPub id when published by update (#915)
|
- Update post's ActivityPub id when published by update (#915)
|
||||||
- Calculate media URI properly even when MEDIA_UPLOAD_DIRECTORY configured (#916)
|
- Calculate media URI properly even when MEDIA_UPLOAD_DIRECTORY configured (#916)
|
||||||
- Prevent duplicated posts in 'all' timeline (#917)
|
- Prevent duplicated posts in 'all' timeline (#917)
|
||||||
- Draw side line for blockquote on start (#933)
|
|
||||||
- Fix URIs of posts on Mastodon (#947)
|
|
||||||
- Place edit link proper position (#956, #963, #964)
|
|
||||||
|
|
||||||
## [[0.6.0]] - 2020-12-29
|
## [[0.6.0]] - 2020-12-29
|
||||||
|
|
||||||
@@ -236,9 +210,7 @@
|
|||||||
- Ability to create multiple blogs
|
- Ability to create multiple blogs
|
||||||
|
|
||||||
<!-- next-url -->
|
<!-- next-url -->
|
||||||
[Unreleased]: https://github.com/Plume-org/Plume/compare/0.7.1...HEAD
|
[Unreleased]: https://github.com/Plume-org/Plume/compare/0.6.0...HEAD
|
||||||
[[0.7.1]]: https://github.com/Plume-org/Plume/compare/0.7.0...0.7.1
|
|
||||||
[[0.7.0]]: https://github.com/Plume-org/Plume/compare/0.6.0...0.7.0
|
|
||||||
[[0.6.0]]: https://github.com/Plume-org/Plume/compare/0.5.0...0.6.0
|
[[0.6.0]]: https://github.com/Plume-org/Plume/compare/0.5.0...0.6.0
|
||||||
[0.5.0]: https://github.com/Plume-org/Plume/compare/0.4.0-alpha-4...0.5.0
|
[0.5.0]: https://github.com/Plume-org/Plume/compare/0.4.0-alpha-4...0.5.0
|
||||||
[0.4.0]: https://github.com/Plume-org/Plume/compare/0.3.0-alpha-2...0.4.0-alpha-4
|
[0.4.0]: https://github.com/Plume-org/Plume/compare/0.3.0-alpha-2...0.4.0-alpha-4
|
||||||
|
|||||||
Generated
+533
-608
File diff suppressed because it is too large
Load Diff
+14
-10
@@ -1,32 +1,36 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
name = "plume"
|
name = "plume"
|
||||||
version = "0.7.1"
|
version = "0.6.1-dev"
|
||||||
repository = "https://github.com/Plume-org/Plume"
|
repository = "https://github.com/Plume-org/Plume"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
activitypub = "0.1.3"
|
activitypub = "0.1.3"
|
||||||
|
askama_escape = "0.1"
|
||||||
atom_syndication = "0.6"
|
atom_syndication = "0.6"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
gettext = { git = "https://github.com/Plume-org/gettext/", rev = "294c54d74c699fbc66502b480a37cc66c1daa7f3" }
|
gettext = { git = "https://github.com/Plume-org/gettext/", rev = "294c54d74c699fbc66502b480a37cc66c1daa7f3" }
|
||||||
gettext-macros = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" }
|
gettext-macros = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" }
|
||||||
gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" }
|
gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" }
|
||||||
guid-create = "0.2"
|
guid-create = "0.1"
|
||||||
|
lettre = "0.9.2"
|
||||||
lettre_email = "0.9.2"
|
lettre_email = "0.9.2"
|
||||||
num_cpus = "1.10"
|
num_cpus = "1.10"
|
||||||
rocket = "0.4.6"
|
rocket = "=0.4.6"
|
||||||
rocket_contrib = { version = "0.4.5", features = ["json"] }
|
rocket_contrib = { version = "=0.4.5", features = ["json"] }
|
||||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
||||||
|
rpassword = "4.0"
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0.70"
|
serde_json = "1.0"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.2.1"
|
||||||
validator = { version = "0.14", features = ["derive"] }
|
validator = "0.8"
|
||||||
|
validator_derive = "0.8"
|
||||||
webfinger = "0.4.1"
|
webfinger = "0.4.1"
|
||||||
tracing = "0.1.22"
|
tracing = "0.1.22"
|
||||||
tracing-subscriber = "0.3.5"
|
tracing-subscriber = "0.2.15"
|
||||||
riker = "0.4.2"
|
riker = "0.4.2"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
@@ -48,7 +52,7 @@ version = "1.4.5"
|
|||||||
[dependencies.multipart]
|
[dependencies.multipart]
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["server"]
|
features = ["server"]
|
||||||
version = "0.18"
|
version = "0.16"
|
||||||
|
|
||||||
[dependencies.plume-api]
|
[dependencies.plume-api]
|
||||||
path = "plume-api"
|
path = "plume-api"
|
||||||
@@ -65,7 +69,7 @@ rev = "29910f2829e7e590a540da3804336577b48c7b31"
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
ructe = "0.13.0"
|
ructe = "0.13.0"
|
||||||
rsass = "0.23"
|
rsass = "0.9"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["postgres"]
|
default = ["postgres"]
|
||||||
|
|||||||
@@ -516,11 +516,4 @@ input:checked ~ .cw-container > .cw-text {
|
|||||||
main .article-meta > *, main .article-meta .comments, main .article-meta > .banner > * {
|
main .article-meta > *, main .article-meta .comments, main .article-meta > .banner > * {
|
||||||
margin: 0 5%;
|
margin: 0 5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-bar {
|
|
||||||
align-items: center;
|
|
||||||
& > div:nth-child(2) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,23 +219,15 @@ p.error {
|
|||||||
margin: 20px;
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cover-link {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover {
|
.cover {
|
||||||
min-height: 10em;
|
min-height: 10em;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
&:hover {
|
||||||
display: flex;
|
opacity: 0.9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@@ -244,14 +236,9 @@ p.error {
|
|||||||
font-family: $playfair;
|
font-family: $playfair;
|
||||||
font-size: 1.75em;
|
font-size: 1.75em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
line-height: 1.10;
|
line-height: 1.75;
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
a {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding-block-start: 0.5em;
|
|
||||||
transition: color 0.1s ease-in;
|
transition: color 0.1s ease-in;
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
|
|
||||||
@@ -260,8 +247,7 @@ p.error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
flex-shrink: 0;
|
float: right;
|
||||||
text-align: end;
|
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
@@ -275,7 +261,7 @@ p.error {
|
|||||||
font-family: $lora;
|
font-family: $lora;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
text-align: initial;
|
text-align: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -479,10 +465,9 @@ figure {
|
|||||||
|
|
||||||
/// Avatars
|
/// Avatars
|
||||||
.avatar {
|
.avatar {
|
||||||
background-position: center !important;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
&.small {
|
&.small {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
@@ -576,6 +561,14 @@ figure {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
& > div {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main .article-meta .comments .comment {
|
main .article-meta .comments .comment {
|
||||||
header {
|
header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ fn main() {
|
|||||||
.expect("compile templates");
|
.expect("compile templates");
|
||||||
|
|
||||||
compile_themes().expect("Theme compilation error");
|
compile_themes().expect("Theme compilation error");
|
||||||
recursive_copy(&Path::new("assets").join("icons"), Path::new("static"))
|
recursive_copy(&Path::new("assets").join("icons"), &Path::new("static"))
|
||||||
.expect("Couldn't copy icons");
|
.expect("Couldn't copy icons");
|
||||||
recursive_copy(&Path::new("assets").join("images"), Path::new("static"))
|
recursive_copy(&Path::new("assets").join("images"), &Path::new("static"))
|
||||||
.expect("Couldn't copy images");
|
.expect("Couldn't copy images");
|
||||||
create_dir_all(&Path::new("static").join("media")).expect("Couldn't init media directory");
|
create_dir_all(&Path::new("static").join("media")).expect("Couldn't init media directory");
|
||||||
|
|
||||||
@@ -97,12 +97,12 @@ fn compile_theme(path: &Path, out_dir: &Path) -> std::io::Result<()> {
|
|||||||
.components()
|
.components()
|
||||||
.skip_while(|c| *c != Component::Normal(OsStr::new("themes")))
|
.skip_while(|c| *c != Component::Normal(OsStr::new("themes")))
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.map(|c| {
|
.filter_map(|c| {
|
||||||
c.as_os_str()
|
c.as_os_str()
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.split_once('.')
|
.splitn(2, '.')
|
||||||
.map_or(c.as_os_str().to_str().unwrap_or_default(), |x| x.0)
|
.next()
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("-");
|
.join("-");
|
||||||
@@ -120,14 +120,8 @@ fn compile_theme(path: &Path, out_dir: &Path) -> std::io::Result<()> {
|
|||||||
// compile the .scss/.sass file
|
// compile the .scss/.sass file
|
||||||
let mut out = File::create(out.join("theme.css"))?;
|
let mut out = File::create(out.join("theme.css"))?;
|
||||||
out.write_all(
|
out.write_all(
|
||||||
&rsass::compile_scss_path(
|
&rsass::compile_scss_file(path, rsass::OutputStyle::Compressed)
|
||||||
path,
|
.expect("SCSS compilation error"),
|
||||||
rsass::output::Format {
|
|
||||||
style: rsass::output::Style::Compressed,
|
|
||||||
..rsass::output::Format::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("SCSS compilation error"),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE instances DROP COLUMN private_key;
|
||||||
|
ALTER TABLE instances DROP COLUMN public_key;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE instances ADD COLUMN private_key TEXT;
|
||||||
|
ALTER TABLE instances ADD COLUMN public_key TEXT;
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE email_signups;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE email_signups (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
email VARCHAR NOT NULL,
|
|
||||||
token VARCHAR NOT NULL,
|
|
||||||
expiration_date TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX email_signups_token ON email_signups (token);
|
|
||||||
CREATE UNIQUE INDEX email_signups_token_requests_email ON email_signups (email);
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
CREATE TABLE instances_old (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
public_domain VARCHAR NOT NULL,
|
||||||
|
name VARCHAR NOT NULL,
|
||||||
|
local BOOLEAN NOT NULL DEFAULT 'f',
|
||||||
|
blocked BOOLEAN NOT NULL DEFAULT 'f',
|
||||||
|
creation_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
open_registrations BOOLEAN NOT NULL DEFAULT 't',
|
||||||
|
short_description TEXT NOT NULL DEFAULT '',
|
||||||
|
long_description TEXT NOT NULL DEFAULT '',
|
||||||
|
default_license TEXT NOT NULL DEFAULT 'CC-0',
|
||||||
|
long_description_html VARCHAR NOT NULL DEFAULT '',
|
||||||
|
short_description_html VARCHAR NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
INSERT INTO instances_old SELECT
|
||||||
|
id,
|
||||||
|
public_domain,
|
||||||
|
name,
|
||||||
|
local,
|
||||||
|
blocked,
|
||||||
|
creation_date,
|
||||||
|
open_registrations,
|
||||||
|
short_description,
|
||||||
|
long_description,
|
||||||
|
default_license,
|
||||||
|
long_description_html,
|
||||||
|
short_description_html
|
||||||
|
FROM instances;
|
||||||
|
DROP TABLE instances;
|
||||||
|
ALTER TABLE instances_old RENAME TO instances;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE instances ADD COLUMN private_key TEXT;
|
||||||
|
ALTER TABLE instances ADD COLUMN public_key TEXT;
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE email_signups;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE email_signups (
|
|
||||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
email VARCHAR NOT NULL,
|
|
||||||
token VARCHAR NOT NULL,
|
|
||||||
expiration_date TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX email_signups_token ON email_signups (token);
|
|
||||||
CREATE UNIQUE INDEX email_signups_token_requests_email ON email_signups (email);
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-api"
|
name = "plume-api"
|
||||||
version = "0.7.1"
|
version = "0.6.1-dev"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-cli"
|
name = "plume-cli"
|
||||||
version = "0.7.1"
|
version = "0.6.1-dev"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
dotenv = "0.15"
|
dotenv = "0.14"
|
||||||
rpassword = "5.0.0"
|
rpassword = "5.0.0"
|
||||||
|
|
||||||
[dependencies.diesel]
|
[dependencies.diesel]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
use plume_models::{instance::*, safe_string::SafeString, Connection};
|
use plume_models::{instance::NewInstance, Connection};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
pub fn command<'a, 'b>() -> App<'a, 'b> {
|
pub fn command<'a, 'b>() -> App<'a, 'b> {
|
||||||
@@ -53,21 +53,5 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
|||||||
.unwrap_or_else(|| String::from("CC-BY-SA"));
|
.unwrap_or_else(|| String::from("CC-BY-SA"));
|
||||||
let open_reg = !args.is_present("private");
|
let open_reg = !args.is_present("private");
|
||||||
|
|
||||||
Instance::insert(
|
NewInstance::new_local(conn, domain, name, open_reg, license).expect("Couldn't save instance");
|
||||||
conn,
|
|
||||||
NewInstance {
|
|
||||||
public_domain: domain,
|
|
||||||
name,
|
|
||||||
local: true,
|
|
||||||
long_description: SafeString::new(""),
|
|
||||||
short_description: SafeString::new(""),
|
|
||||||
default_license: license,
|
|
||||||
open_registrations: open_reg,
|
|
||||||
short_description_html: String::new(),
|
|
||||||
long_description_html: String::new(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("Couldn't save instance");
|
|
||||||
Instance::cache_local(conn);
|
|
||||||
Instance::create_local_instance_user(conn).expect("Couldn't save local instance user");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ fn main() {
|
|||||||
e => e.map(|_| ()).unwrap(),
|
e => e.map(|_| ()).unwrap(),
|
||||||
}
|
}
|
||||||
let conn = Conn::establish(CONFIG.database_url.as_str());
|
let conn = Conn::establish(CONFIG.database_url.as_str());
|
||||||
let _ = conn.as_ref().map(Instance::cache_local);
|
let _ = conn.as_ref().map(|conn| Instance::cache_local(conn));
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
("instance", Some(args)) => {
|
("instance", Some(args)) => {
|
||||||
|
|||||||
+7
-12
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-common"
|
name = "plume-common"
|
||||||
version = "0.7.1"
|
version = "0.6.1-dev"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -9,21 +9,21 @@ activitypub = "0.1.1"
|
|||||||
activitystreams-derive = "0.1.1"
|
activitystreams-derive = "0.1.1"
|
||||||
activitystreams-traits = "0.1.0"
|
activitystreams-traits = "0.1.0"
|
||||||
array_tool = "1.0"
|
array_tool = "1.0"
|
||||||
base64 = "0.13"
|
base64 = "0.10"
|
||||||
heck = "0.4.0"
|
heck = "0.3.0"
|
||||||
hex = "0.4"
|
hex = "0.3"
|
||||||
|
hyper = "0.12.33"
|
||||||
openssl = "0.10.22"
|
openssl = "0.10.22"
|
||||||
rocket = "0.4.6"
|
rocket = "=0.4.6"
|
||||||
reqwest = { version = "0.9", features = ["socks"] }
|
reqwest = { version = "0.9", features = ["socks"] }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.70"
|
serde_json = "1.0"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.3.0"
|
||||||
syntect = "4.5.0"
|
syntect = "4.5.0"
|
||||||
tokio = "0.1.22"
|
tokio = "0.1.22"
|
||||||
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] }
|
regex-syntax = { version = "0.6.17", default-features = false, features = ["unicode-perl"] }
|
||||||
tracing = "0.1.22"
|
tracing = "0.1.22"
|
||||||
askama_escape = "0.10.2"
|
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
@@ -33,8 +33,3 @@ version = "0.4"
|
|||||||
default-features = false
|
default-features = false
|
||||||
git = "https://git.joinplu.me/Plume/pulldown-cmark"
|
git = "https://git.joinplu.me/Plume/pulldown-cmark"
|
||||||
branch = "bidi-plume"
|
branch = "bidi-plume"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
once_cell = "1.5.2"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use reqwest;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use super::{request, sign::Signer};
|
use super::{request, sign::Signer};
|
||||||
|
use reqwest::{
|
||||||
|
header::{HeaderValue, HOST},
|
||||||
|
Url,
|
||||||
|
};
|
||||||
|
|
||||||
/// Represents an ActivityPub inbox.
|
/// Represents an ActivityPub inbox.
|
||||||
///
|
///
|
||||||
@@ -13,49 +16,7 @@ use super::{request, sign::Signer};
|
|||||||
/// # extern crate activitypub;
|
/// # extern crate activitypub;
|
||||||
/// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note};
|
/// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note};
|
||||||
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
/// # use once_cell::sync::Lazy;
|
/// # use plume_common::activity_pub::{inbox::*, sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer}};
|
||||||
/// # use plume_common::activity_pub::inbox::*;
|
|
||||||
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
|
|
||||||
/// #
|
|
||||||
/// # static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
|
||||||
/// #
|
|
||||||
/// # struct MySigner {
|
|
||||||
/// # public_key: String,
|
|
||||||
/// # private_key: String,
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # impl MySigner {
|
|
||||||
/// # fn new() -> Self {
|
|
||||||
/// # let (pub_key, priv_key) = gen_keypair();
|
|
||||||
/// # Self {
|
|
||||||
/// # public_key: String::from_utf8(pub_key).unwrap(),
|
|
||||||
/// # private_key: String::from_utf8(priv_key).unwrap(),
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # impl Signer for MySigner {
|
|
||||||
/// # fn get_key_id(&self) -> String {
|
|
||||||
/// # "mysigner".into()
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
|
||||||
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
|
||||||
/// # .unwrap();
|
|
||||||
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
|
||||||
/// # signer.update(to_sign.as_bytes()).unwrap();
|
|
||||||
/// # signer.sign_to_vec().map_err(|_| SignError())
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
|
||||||
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
|
||||||
/// # .unwrap();
|
|
||||||
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
|
||||||
/// # verifier.update(data.as_bytes()).unwrap();
|
|
||||||
/// # verifier.verify(&signature).map_err(|_| SignError())
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # struct User;
|
/// # struct User;
|
||||||
/// # impl FromId<()> for User {
|
/// # impl FromId<()> for User {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@@ -68,10 +29,6 @@ use super::{request, sign::Signer};
|
|||||||
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(User)
|
/// # Ok(User)
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
|
||||||
/// # fn get_sender() -> &'static dyn Signer {
|
|
||||||
/// # &*MY_SIGNER
|
|
||||||
/// # }
|
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsActor<&()> for User {
|
/// # impl AsActor<&()> for User {
|
||||||
/// # fn get_inbox_url(&self) -> String {
|
/// # fn get_inbox_url(&self) -> String {
|
||||||
@@ -91,10 +48,6 @@ use super::{request, sign::Signer};
|
|||||||
/// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(Message)
|
/// # Ok(Message)
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
|
||||||
/// # fn get_sender() -> &'static dyn Signer {
|
|
||||||
/// # &*MY_SIGNER
|
|
||||||
/// # }
|
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsObject<User, Create, &()> for Message {
|
/// # impl AsObject<User, Create, &()> for Message {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@@ -112,6 +65,42 @@ use super::{request, sign::Signer};
|
|||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
|
/// # struct MySigner {
|
||||||
|
/// # public_key: String,
|
||||||
|
/// # private_key: String,
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl MySigner {
|
||||||
|
/// # fn new() -> Self {
|
||||||
|
/// # let (pub_key, priv_key) = gen_keypair();
|
||||||
|
/// # Self {
|
||||||
|
/// # public_key: String::from_utf8(pub_key).unwrap(),
|
||||||
|
/// # private_key: String::from_utf8(priv_key).unwrap(),
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl Signer for MySigner {
|
||||||
|
/// # fn get_key_id(&self) -> String {
|
||||||
|
/// # "mysigner".into()
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
|
||||||
|
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
||||||
|
/// # .unwrap();
|
||||||
|
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
/// # signer.update(to_sign.as_bytes()).unwrap();
|
||||||
|
/// # signer.sign_to_vec().map_err(|_| SignatureError())
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
|
||||||
|
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
||||||
|
/// # .unwrap();
|
||||||
|
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
/// # verifier.update(data.as_bytes()).unwrap();
|
||||||
|
/// # verifier.verify(&signature).map_err(|_| SignatureError())
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
/// #
|
/// #
|
||||||
/// # let mut act = Create::default();
|
/// # let mut act = Create::default();
|
||||||
/// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap();
|
/// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap();
|
||||||
@@ -122,8 +111,9 @@ use super::{request, sign::Signer};
|
|||||||
/// # let activity_json = serde_json::to_value(act).unwrap();
|
/// # let activity_json = serde_json::to_value(act).unwrap();
|
||||||
/// #
|
/// #
|
||||||
/// # let conn = ();
|
/// # let conn = ();
|
||||||
|
/// # let sender = MySigner::new();
|
||||||
/// #
|
/// #
|
||||||
/// let result: Result<(), ()> = Inbox::handle(&conn, activity_json)
|
/// let result: Result<(), ()> = Inbox::handle(&conn, &sender, activity_json)
|
||||||
/// .with::<User, Announce, Message>(None)
|
/// .with::<User, Announce, Message>(None)
|
||||||
/// .with::<User, Create, Message>(None)
|
/// .with::<User, Create, Message>(None)
|
||||||
/// .done();
|
/// .done();
|
||||||
@@ -137,9 +127,10 @@ where
|
|||||||
/// # Structure
|
/// # Structure
|
||||||
///
|
///
|
||||||
/// - the context to be passed to each handler.
|
/// - the context to be passed to each handler.
|
||||||
|
/// - the sender actor to sign request
|
||||||
/// - the activity
|
/// - the activity
|
||||||
/// - the reason it has not been handled yet
|
/// - the reason it has not been handled yet
|
||||||
NotHandled(&'a C, serde_json::Value, InboxError<E>),
|
NotHandled(&'a C, &'a dyn Signer, serde_json::Value, InboxError<E>),
|
||||||
|
|
||||||
/// A matching handler have been found but failed
|
/// A matching handler have been found but failed
|
||||||
///
|
///
|
||||||
@@ -192,8 +183,12 @@ where
|
|||||||
///
|
///
|
||||||
/// - `ctx`: the context to pass to each handler
|
/// - `ctx`: the context to pass to each handler
|
||||||
/// - `json`: the JSON representation of the incoming activity
|
/// - `json`: the JSON representation of the incoming activity
|
||||||
pub fn handle(ctx: &'a C, json: serde_json::Value) -> Inbox<'a, C, E, R> {
|
pub fn handle(
|
||||||
Inbox::NotHandled(ctx, json, InboxError::NoMatch)
|
ctx: &'a C,
|
||||||
|
sender: &'a dyn Signer,
|
||||||
|
json: serde_json::Value,
|
||||||
|
) -> Inbox<'a, C, E, R> {
|
||||||
|
Inbox::NotHandled(ctx, sender, json, InboxError::NoMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers an handler on this Inbox.
|
/// Registers an handler on this Inbox.
|
||||||
@@ -204,27 +199,30 @@ where
|
|||||||
M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>,
|
M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>,
|
||||||
M::Output: Into<R>,
|
M::Output: Into<R>,
|
||||||
{
|
{
|
||||||
if let Inbox::NotHandled(ctx, mut act, e) = self {
|
if let Inbox::NotHandled(ctx, sender, mut act, e) = self {
|
||||||
if serde_json::from_value::<V>(act.clone()).is_ok() {
|
if serde_json::from_value::<V>(act.clone()).is_ok() {
|
||||||
let act_clone = act.clone();
|
let act_clone = act.clone();
|
||||||
let act_id = match act_clone["id"].as_str() {
|
let act_id = match act_clone["id"].as_str() {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidID),
|
None => return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidID),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the actor ID
|
// Get the actor ID
|
||||||
let actor_id = match get_id(act["actor"].clone()) {
|
let actor_id = match get_id(act["actor"].clone()) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)),
|
None => {
|
||||||
|
return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidActor(None))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if Self::is_spoofed_activity(&actor_id, &act) {
|
if Self::is_spoofed_activity(&actor_id, &act) {
|
||||||
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None));
|
return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidObject(None));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform this actor to a model (see FromId for details about the from_id function)
|
// Transform this actor to a model (see FromId for details about the from_id function)
|
||||||
let actor = match A::from_id(
|
let actor = match A::from_id(
|
||||||
ctx,
|
ctx,
|
||||||
|
sender,
|
||||||
&actor_id,
|
&actor_id,
|
||||||
serde_json::from_value(act["actor"].clone()).ok(),
|
serde_json::from_value(act["actor"].clone()).ok(),
|
||||||
proxy,
|
proxy,
|
||||||
@@ -235,17 +233,25 @@ where
|
|||||||
if let Some(json) = json {
|
if let Some(json) = json {
|
||||||
act["actor"] = json;
|
act["actor"] = json;
|
||||||
}
|
}
|
||||||
return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(Some(e)));
|
return Inbox::NotHandled(
|
||||||
|
ctx,
|
||||||
|
sender,
|
||||||
|
act,
|
||||||
|
InboxError::InvalidActor(Some(e)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Same logic for "object"
|
// Same logic for "object"
|
||||||
let obj_id = match get_id(act["object"].clone()) {
|
let obj_id = match get_id(act["object"].clone()) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None)),
|
None => {
|
||||||
|
return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidObject(None))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let obj = match M::from_id(
|
let obj = match M::from_id(
|
||||||
ctx,
|
ctx,
|
||||||
|
sender,
|
||||||
&obj_id,
|
&obj_id,
|
||||||
serde_json::from_value(act["object"].clone()).ok(),
|
serde_json::from_value(act["object"].clone()).ok(),
|
||||||
proxy,
|
proxy,
|
||||||
@@ -255,19 +261,24 @@ where
|
|||||||
if let Some(json) = json {
|
if let Some(json) = json {
|
||||||
act["object"] = json;
|
act["object"] = json;
|
||||||
}
|
}
|
||||||
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(Some(e)));
|
return Inbox::NotHandled(
|
||||||
|
ctx,
|
||||||
|
sender,
|
||||||
|
act,
|
||||||
|
InboxError::InvalidObject(Some(e)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle the activity
|
// Handle the activity
|
||||||
match obj.activity(ctx, actor, act_id) {
|
match obj.activity(ctx, actor, &act_id) {
|
||||||
Ok(res) => Inbox::Handled(res.into()),
|
Ok(res) => Inbox::Handled(res.into()),
|
||||||
Err(e) => Inbox::Failed(e),
|
Err(e) => Inbox::Failed(e),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the Activity type is not matching the expected one for
|
// If the Activity type is not matching the expected one for
|
||||||
// this handler, try with the next one.
|
// this handler, try with the next one.
|
||||||
Inbox::NotHandled(ctx, act, e)
|
Inbox::NotHandled(ctx, sender, act, e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
@@ -278,7 +289,7 @@ where
|
|||||||
pub fn done(self) -> Result<R, E> {
|
pub fn done(self) -> Result<R, E> {
|
||||||
match self {
|
match self {
|
||||||
Inbox::Handled(res) => Ok(res),
|
Inbox::Handled(res) => Ok(res),
|
||||||
Inbox::NotHandled(_, _, err) => Err(E::from(err)),
|
Inbox::NotHandled(_, _, _, err) => Err(E::from(err)),
|
||||||
Inbox::Failed(err) => Err(err),
|
Inbox::Failed(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,6 +356,7 @@ pub trait FromId<C>: Sized {
|
|||||||
/// If absent, the ID will be dereferenced.
|
/// If absent, the ID will be dereferenced.
|
||||||
fn from_id(
|
fn from_id(
|
||||||
ctx: &C,
|
ctx: &C,
|
||||||
|
sender: &dyn Signer,
|
||||||
id: &str,
|
id: &str,
|
||||||
object: Option<Self::Object>,
|
object: Option<Self::Object>,
|
||||||
proxy: Option<&reqwest::Proxy>,
|
proxy: Option<&reqwest::Proxy>,
|
||||||
@@ -353,7 +365,7 @@ pub trait FromId<C>: Sized {
|
|||||||
Ok(x) => Ok(x),
|
Ok(x) => Ok(x),
|
||||||
_ => match object {
|
_ => match object {
|
||||||
Some(o) => Self::from_activity(ctx, o).map_err(|e| (None, e)),
|
Some(o) => Self::from_activity(ctx, o).map_err(|e| (None, e)),
|
||||||
None => Self::from_activity(ctx, Self::deref(id, proxy.cloned())?)
|
None => Self::from_activity(ctx, Self::deref(id, sender, proxy.cloned())?)
|
||||||
.map_err(|e| (None, e)),
|
.map_err(|e| (None, e)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -362,18 +374,42 @@ pub trait FromId<C>: Sized {
|
|||||||
/// Dereferences an ID
|
/// Dereferences an ID
|
||||||
fn deref(
|
fn deref(
|
||||||
id: &str,
|
id: &str,
|
||||||
|
sender: &dyn Signer,
|
||||||
proxy: Option<reqwest::Proxy>,
|
proxy: Option<reqwest::Proxy>,
|
||||||
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
||||||
request::get(id, Self::get_sender(), proxy)
|
let mut headers = request::headers();
|
||||||
.map_err(|_| (None, InboxError::DerefError))
|
let url = Url::parse(&id).map_err(|_| (None, InboxError::InvalidID.into()))?;
|
||||||
.and_then(|mut r| {
|
if !url.has_host() {
|
||||||
let json: serde_json::Value = r
|
return Err((None, InboxError::InvalidID.into()));
|
||||||
.json()
|
}
|
||||||
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable"))
|
||||||
serde_json::from_value(json.clone())
|
.map_err(|_| (None, InboxError::DerefError.into()))?;
|
||||||
.map_err(|_| (Some(json), InboxError::InvalidObject(None)))
|
headers.insert(HOST, host_header_value);
|
||||||
})
|
if let Some(proxy) = proxy {
|
||||||
.map_err(|(json, e)| (json, e.into()))
|
reqwest::ClientBuilder::new().proxy(proxy)
|
||||||
|
} else {
|
||||||
|
reqwest::ClientBuilder::new()
|
||||||
|
}
|
||||||
|
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
||||||
|
.build()
|
||||||
|
.map_err(|_| (None, InboxError::DerefError.into()))?
|
||||||
|
.get(id)
|
||||||
|
.headers(headers.clone())
|
||||||
|
.header(
|
||||||
|
"Signature",
|
||||||
|
request::signature(sender, &headers, ("get", url.path(), url.query()))
|
||||||
|
.map_err(|_| (None, InboxError::DerefError.into()))?,
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.map_err(|_| (None, InboxError::DerefError))
|
||||||
|
.and_then(|mut r| {
|
||||||
|
let json: serde_json::Value = r
|
||||||
|
.json()
|
||||||
|
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
||||||
|
serde_json::from_value(json.clone())
|
||||||
|
.map_err(|_| (Some(json), InboxError::InvalidObject(None)))
|
||||||
|
})
|
||||||
|
.map_err(|(json, e)| (json, e.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a `Self` from its ActivityPub representation
|
/// Builds a `Self` from its ActivityPub representation
|
||||||
@@ -381,8 +417,6 @@ pub trait FromId<C>: Sized {
|
|||||||
|
|
||||||
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
|
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
|
||||||
fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
|
fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should be implemented by anything representing an ActivityPub actor.
|
/// Should be implemented by anything representing an ActivityPub actor.
|
||||||
@@ -421,49 +455,6 @@ pub trait AsActor<C> {
|
|||||||
/// # extern crate activitypub;
|
/// # extern crate activitypub;
|
||||||
/// # use activitypub::{activity::Create, actor::Person, object::Note};
|
/// # use activitypub::{activity::Create, actor::Person, object::Note};
|
||||||
/// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId};
|
/// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId};
|
||||||
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
|
|
||||||
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
|
||||||
/// # use once_cell::sync::Lazy;
|
|
||||||
/// #
|
|
||||||
/// # static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
|
||||||
/// #
|
|
||||||
/// # struct MySigner {
|
|
||||||
/// # public_key: String,
|
|
||||||
/// # private_key: String,
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # impl MySigner {
|
|
||||||
/// # fn new() -> Self {
|
|
||||||
/// # let (pub_key, priv_key) = gen_keypair();
|
|
||||||
/// # Self {
|
|
||||||
/// # public_key: String::from_utf8(pub_key).unwrap(),
|
|
||||||
/// # private_key: String::from_utf8(priv_key).unwrap(),
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # impl Signer for MySigner {
|
|
||||||
/// # fn get_key_id(&self) -> String {
|
|
||||||
/// # "mysigner".into()
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
|
||||||
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
|
||||||
/// # .unwrap();
|
|
||||||
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
|
||||||
/// # signer.update(to_sign.as_bytes()).unwrap();
|
|
||||||
/// # signer.sign_to_vec().map_err(|_| SignError())
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
|
||||||
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
|
||||||
/// # .unwrap();
|
|
||||||
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
|
||||||
/// # verifier.update(data.as_bytes()).unwrap();
|
|
||||||
/// # verifier.verify(&signature).map_err(|_| SignError())
|
|
||||||
/// # }
|
|
||||||
/// # }
|
|
||||||
/// #
|
|
||||||
/// # struct Account;
|
/// # struct Account;
|
||||||
/// # impl FromId<()> for Account {
|
/// # impl FromId<()> for Account {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@@ -476,10 +467,6 @@ pub trait AsActor<C> {
|
|||||||
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(Account)
|
/// # Ok(Account)
|
||||||
/// # }
|
/// # }
|
||||||
/// #
|
|
||||||
/// # fn get_sender() -> &'static dyn Signer {
|
|
||||||
/// # &*MY_SIGNER
|
|
||||||
/// # }
|
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsActor<()> for Account {
|
/// # impl AsActor<()> for Account {
|
||||||
/// # fn get_inbox_url(&self) -> String {
|
/// # fn get_inbox_url(&self) -> String {
|
||||||
@@ -503,10 +490,6 @@ pub trait AsActor<C> {
|
|||||||
/// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
/// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
||||||
/// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? })
|
/// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? })
|
||||||
/// }
|
/// }
|
||||||
///
|
|
||||||
/// fn get_sender() -> &'static dyn Signer {
|
|
||||||
/// &*MY_SIGNER
|
|
||||||
/// }
|
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl AsObject<Account, Create, ()> for Message {
|
/// impl AsObject<Account, Create, ()> for Message {
|
||||||
@@ -545,53 +528,11 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::super::sign::{gen_keypair, Error as SignatureError, Result as SignatureResult};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::activity_pub::sign::{
|
|
||||||
gen_keypair, Error as SignError, Result as SignResult, Signer,
|
|
||||||
};
|
|
||||||
use activitypub::{activity::*, actor::Person, object::Note};
|
use activitypub::{activity::*, actor::Person, object::Note};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
|
|
||||||
static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
|
||||||
|
|
||||||
struct MySigner {
|
|
||||||
public_key: String,
|
|
||||||
private_key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MySigner {
|
|
||||||
fn new() -> Self {
|
|
||||||
let (pub_key, priv_key) = gen_keypair();
|
|
||||||
Self {
|
|
||||||
public_key: String::from_utf8(pub_key).unwrap(),
|
|
||||||
private_key: String::from_utf8(priv_key).unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Signer for MySigner {
|
|
||||||
fn get_key_id(&self) -> String {
|
|
||||||
"mysigner".into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
|
||||||
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
|
||||||
signer.update(to_sign.as_bytes()).unwrap();
|
|
||||||
signer.sign_to_vec().map_err(|_| SignError())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
|
||||||
verifier.update(data.as_bytes()).unwrap();
|
|
||||||
verifier.verify(&signature).map_err(|_| SignError())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MyActor;
|
struct MyActor;
|
||||||
impl FromId<()> for MyActor {
|
impl FromId<()> for MyActor {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
@@ -604,10 +545,6 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
||||||
Ok(MyActor)
|
Ok(MyActor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
&*MY_SIGNER
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&()> for MyActor {
|
impl AsActor<&()> for MyActor {
|
||||||
@@ -632,10 +569,6 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
|
||||||
Ok(MyObject)
|
Ok(MyObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
&*MY_SIGNER
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl AsObject<MyActor, Create, &()> for MyObject {
|
impl AsObject<MyActor, Create, &()> for MyObject {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
@@ -696,10 +629,47 @@ mod tests {
|
|||||||
act
|
act
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MySigner {
|
||||||
|
public_key: String,
|
||||||
|
private_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MySigner {
|
||||||
|
fn new() -> Self {
|
||||||
|
let (pub_key, priv_key) = gen_keypair();
|
||||||
|
Self {
|
||||||
|
public_key: String::from_utf8(pub_key).unwrap(),
|
||||||
|
private_key: String::from_utf8(priv_key).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signer for MySigner {
|
||||||
|
fn get_key_id(&self) -> String {
|
||||||
|
"mysigner".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
|
||||||
|
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
signer.update(to_sign.as_bytes()).unwrap();
|
||||||
|
signer.sign_to_vec().map_err(|_| SignatureError())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
|
||||||
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
verifier.update(data.as_bytes()).unwrap();
|
||||||
|
verifier.verify(&signature).map_err(|_| SignatureError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_inbox_basic() {
|
fn test_inbox_basic() {
|
||||||
let act = serde_json::to_value(build_create()).unwrap();
|
let act = serde_json::to_value(build_create()).unwrap();
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act)
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
|
||||||
.with::<MyActor, Create, MyObject>(None)
|
.with::<MyActor, Create, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
@@ -708,7 +678,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_inbox_multi_handlers() {
|
fn test_inbox_multi_handlers() {
|
||||||
let act = serde_json::to_value(build_create()).unwrap();
|
let act = serde_json::to_value(build_create()).unwrap();
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act)
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
|
||||||
.with::<MyActor, Announce, MyObject>(None)
|
.with::<MyActor, Announce, MyObject>(None)
|
||||||
.with::<MyActor, Delete, MyObject>(None)
|
.with::<MyActor, Delete, MyObject>(None)
|
||||||
.with::<MyActor, Create, MyObject>(None)
|
.with::<MyActor, Create, MyObject>(None)
|
||||||
@@ -721,7 +691,7 @@ mod tests {
|
|||||||
fn test_inbox_failure() {
|
fn test_inbox_failure() {
|
||||||
let act = serde_json::to_value(build_create()).unwrap();
|
let act = serde_json::to_value(build_create()).unwrap();
|
||||||
// Create is not handled by this inbox
|
// Create is not handled by this inbox
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act)
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
|
||||||
.with::<MyActor, Announce, MyObject>(None)
|
.with::<MyActor, Announce, MyObject>(None)
|
||||||
.with::<MyActor, Like, MyObject>(None)
|
.with::<MyActor, Like, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
@@ -740,10 +710,6 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
&*MY_SIGNER
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl AsActor<&()> for FailingActor {
|
impl AsActor<&()> for FailingActor {
|
||||||
fn get_inbox_url(&self) -> String {
|
fn get_inbox_url(&self) -> String {
|
||||||
@@ -774,12 +740,12 @@ mod tests {
|
|||||||
fn test_inbox_actor_failure() {
|
fn test_inbox_actor_failure() {
|
||||||
let act = serde_json::to_value(build_create()).unwrap();
|
let act = serde_json::to_value(build_create()).unwrap();
|
||||||
|
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act.clone())
|
||||||
.with::<FailingActor, Create, MyObject>(None)
|
.with::<FailingActor, Create, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
|
||||||
let res: Result<(), ()> = Inbox::handle(&(), act.clone())
|
let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act.clone())
|
||||||
.with::<FailingActor, Create, MyObject>(None)
|
.with::<FailingActor, Create, MyObject>(None)
|
||||||
.with::<MyActor, Create, MyObject>(None)
|
.with::<MyActor, Create, MyObject>(None)
|
||||||
.done();
|
.done();
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ where
|
|||||||
warn!("Inbox doesn't have host: {:?}", &inbox);
|
warn!("Inbox doesn't have host: {:?}", &inbox);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable"));
|
let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable"));
|
||||||
if host_header_value.is_err() {
|
if host_header_value.is_err() {
|
||||||
warn!("Header value is invalid: {:?}", url.host_str());
|
warn!("Header value is invalid: {:?}", url.host_str());
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
use chrono::{offset::Utc, DateTime};
|
use chrono::{offset::Utc, DateTime};
|
||||||
use openssl::hash::{Hasher, MessageDigest};
|
use openssl::hash::{Hasher, MessageDigest};
|
||||||
use reqwest::{
|
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT};
|
||||||
header::{
|
|
||||||
HeaderMap, HeaderValue, InvalidHeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT,
|
|
||||||
},
|
|
||||||
ClientBuilder, Proxy, Response, Url, UrlError,
|
|
||||||
};
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
@@ -18,24 +13,6 @@ const PLUME_USER_AGENT: &str = concat!("Plume/", env!("CARGO_PKG_VERSION"));
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error();
|
pub struct Error();
|
||||||
|
|
||||||
impl From<UrlError> for Error {
|
|
||||||
fn from(_err: UrlError) -> Self {
|
|
||||||
Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<InvalidHeaderValue> for Error {
|
|
||||||
fn from(_err: InvalidHeaderValue) -> Self {
|
|
||||||
Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<reqwest::Error> for Error {
|
|
||||||
fn from(_err: reqwest::Error) -> Self {
|
|
||||||
Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Digest(String);
|
pub struct Digest(String);
|
||||||
|
|
||||||
impl Digest {
|
impl Digest {
|
||||||
@@ -187,35 +164,12 @@ pub fn signature(
|
|||||||
)).map_err(|_| Error())
|
)).map_err(|_| Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(url_str: &str, sender: &dyn Signer, proxy: Option<Proxy>) -> Result<Response, Error> {
|
|
||||||
let mut headers = headers();
|
|
||||||
let url = Url::parse(url_str)?;
|
|
||||||
if !url.has_host() {
|
|
||||||
return Err(Error());
|
|
||||||
}
|
|
||||||
let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable"))?;
|
|
||||||
headers.insert(HOST, host_header_value);
|
|
||||||
if let Some(proxy) = proxy {
|
|
||||||
ClientBuilder::new().proxy(proxy)
|
|
||||||
} else {
|
|
||||||
ClientBuilder::new()
|
|
||||||
}
|
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
|
||||||
.build()?
|
|
||||||
.get(url_str)
|
|
||||||
.headers(headers.clone())
|
|
||||||
.header(
|
|
||||||
"Signature",
|
|
||||||
signature(sender, &headers, ("get", url.path(), url.query()))?,
|
|
||||||
)
|
|
||||||
.send()
|
|
||||||
.map_err(|_| Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::signature;
|
use super::signature;
|
||||||
use crate::activity_pub::sign::{gen_keypair, Error, Result, Signer};
|
use crate::activity_pub::sign::{
|
||||||
|
gen_keypair, Error as SignatureError, Result as SignatureResult, Signer,
|
||||||
|
};
|
||||||
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
|
|
||||||
@@ -239,20 +193,20 @@ mod tests {
|
|||||||
"mysigner".into()
|
"mysigner".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
|
fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
|
||||||
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
signer.update(to_sign.as_bytes()).unwrap();
|
signer.update(to_sign.as_bytes()).unwrap();
|
||||||
signer.sign_to_vec().map_err(|_| Error())
|
signer.sign_to_vec().map_err(|_| SignatureError())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
verifier.update(data.as_bytes()).unwrap();
|
verifier.update(data.as_bytes()).unwrap();
|
||||||
verifier.verify(&signature).map_err(|_| Error())
|
verifier.verify(&signature).map_err(|_| SignatureError())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>(
|
|||||||
}
|
}
|
||||||
let digest = all_headers.get_one("digest").unwrap_or("");
|
let digest = all_headers.get_one("digest").unwrap_or("");
|
||||||
let digest = request::Digest::from_header(digest);
|
let digest = request::Digest::from_header(digest);
|
||||||
if !digest.map(|d| d.verify_header(data)).unwrap_or(false) {
|
if !digest.map(|d| d.verify_header(&data)).unwrap_or(false) {
|
||||||
// signature was valid, but body content does not match its digest
|
// signature was valid, but body content does not match its digest
|
||||||
return SignatureValidity::Invalid;
|
return SignatureValidity::Invalid;
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-12
@@ -1,8 +1,11 @@
|
|||||||
use heck::ToUpperCamelCase;
|
use heck::CamelCase;
|
||||||
use openssl::rand::rand_bytes;
|
use openssl::rand::rand_bytes;
|
||||||
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag};
|
use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag};
|
||||||
use regex_syntax::is_word_character;
|
use regex_syntax::is_word_character;
|
||||||
use rocket::http::uri::Uri;
|
use rocket::{
|
||||||
|
http::uri::Uri,
|
||||||
|
response::{Flash, Redirect},
|
||||||
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use syntect::html::{ClassStyle, ClassedHTMLGenerator};
|
use syntect::html::{ClassStyle, ClassedHTMLGenerator};
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
@@ -18,7 +21,7 @@ pub fn random_hex() -> String {
|
|||||||
|
|
||||||
/// Remove non alphanumeric characters and CamelCase a string
|
/// Remove non alphanumeric characters and CamelCase a string
|
||||||
pub fn make_actor_id(name: &str) -> String {
|
pub fn make_actor_id(name: &str) -> String {
|
||||||
name.to_upper_camel_case()
|
name.to_camel_case()
|
||||||
.chars()
|
.chars()
|
||||||
.filter(|c| c.is_alphanumeric())
|
.filter(|c| c.is_alphanumeric())
|
||||||
.collect()
|
.collect()
|
||||||
@@ -77,6 +80,19 @@ pub fn iri_percent_encode_seg_char(c: char) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects to the login page with a given message.
|
||||||
|
*
|
||||||
|
* Note that the message should be translated before passed to this function.
|
||||||
|
*/
|
||||||
|
pub fn requires_login<T: Into<Uri<'static>>>(message: &str, url: T) -> Flash<Redirect> {
|
||||||
|
Flash::new(
|
||||||
|
Redirect::to(format!("/login?m={}", Uri::percent_encode(message))),
|
||||||
|
"callback",
|
||||||
|
url.into().to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum State {
|
enum State {
|
||||||
Mention,
|
Mention,
|
||||||
@@ -125,13 +141,13 @@ fn highlight_code<'a>(
|
|||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
let syntax_set = SyntaxSet::load_defaults_newlines();
|
let syntax_set = SyntaxSet::load_defaults_newlines();
|
||||||
let syntax = syntax_set.find_syntax_by_token(lang).unwrap_or_else(|| {
|
let syntax = syntax_set.find_syntax_by_token(&lang).unwrap_or_else(|| {
|
||||||
syntax_set
|
syntax_set
|
||||||
.find_syntax_by_name(lang)
|
.find_syntax_by_name(&lang)
|
||||||
.unwrap_or_else(|| syntax_set.find_syntax_plain_text())
|
.unwrap_or_else(|| syntax_set.find_syntax_plain_text())
|
||||||
});
|
});
|
||||||
let mut html = ClassedHTMLGenerator::new_with_class_style(
|
let mut html = ClassedHTMLGenerator::new_with_class_style(
|
||||||
syntax,
|
&syntax,
|
||||||
&syntax_set,
|
&syntax_set,
|
||||||
ClassStyle::Spaced,
|
ClassStyle::Spaced,
|
||||||
);
|
);
|
||||||
@@ -318,15 +334,16 @@ pub fn md_to_html<'a>(
|
|||||||
text_acc.push(c)
|
text_acc.push(c)
|
||||||
}
|
}
|
||||||
let mention = text_acc;
|
let mention = text_acc;
|
||||||
|
let short_mention = mention.splitn(1, '@').next().unwrap_or("");
|
||||||
let link = Tag::Link(
|
let link = Tag::Link(
|
||||||
LinkType::Inline,
|
LinkType::Inline,
|
||||||
format!("{}@/{}/", base_url, &mention).into(),
|
format!("{}@/{}/", base_url, &mention).into(),
|
||||||
mention.clone().into(),
|
short_mention.to_owned().into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
mentions.push(mention.clone());
|
mentions.push(mention.clone());
|
||||||
events.push(Event::Start(link.clone()));
|
events.push(Event::Start(link.clone()));
|
||||||
events.push(Event::Text(format!("@{}", &mention).into()));
|
events.push(Event::Text(format!("@{}", &short_mention).into()));
|
||||||
events.push(Event::End(link));
|
events.push(Event::End(link));
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -450,10 +467,6 @@ pub fn md_to_html<'a>(
|
|||||||
(buf, mentions.collect(), hashtags.collect())
|
(buf, mentions.collect(), hashtags.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn escape(string: &str) -> askama_escape::Escaped<askama_escape::Html> {
|
|
||||||
askama_escape::escape(string, askama_escape::Html)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-front"
|
name = "plume-front"
|
||||||
version = "0.7.1"
|
version = "0.6.1-dev"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
+14
-20
@@ -54,6 +54,11 @@ pub enum EditorError {
|
|||||||
DOMError,
|
DOMError,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::option::NoneError> for EditorError {
|
||||||
|
fn from(_: std::option::NoneError) -> Self {
|
||||||
|
EditorError::NoneError
|
||||||
|
}
|
||||||
|
}
|
||||||
const AUTOSAVE_DEBOUNCE_TIME: i32 = 5000;
|
const AUTOSAVE_DEBOUNCE_TIME: i32 = 5000;
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct AutosaveInformation {
|
struct AutosaveInformation {
|
||||||
@@ -193,7 +198,7 @@ fn clear_autosave() {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.remove_item(&get_autosave_id())
|
.remove_item(&get_autosave_id())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
console::log_1(&format!("Saved to {}", &get_autosave_id()).into());
|
console::log_1(&&format!("Saved to {}", &get_autosave_id()).into());
|
||||||
}
|
}
|
||||||
type TimeoutHandle = i32;
|
type TimeoutHandle = i32;
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@@ -361,9 +366,7 @@ fn init_editor() -> Result<(), EditorError> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let old_ed = old_ed.unwrap();
|
let old_ed = old_ed.unwrap();
|
||||||
let old_title = document()
|
let old_title = document().get_element_by_id("plume-editor-title")?;
|
||||||
.get_element_by_id("plume-editor-title")
|
|
||||||
.ok_or(EditorError::NoneError)?;
|
|
||||||
old_ed
|
old_ed
|
||||||
.dyn_ref::<HtmlElement>()
|
.dyn_ref::<HtmlElement>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -431,8 +434,7 @@ fn init_editor() -> Result<(), EditorError> {
|
|||||||
bg.class_list().add_1("show").unwrap();
|
bg.class_list().add_1("show").unwrap();
|
||||||
})) as Box<dyn FnMut(MouseEvent)>);
|
})) as Box<dyn FnMut(MouseEvent)>);
|
||||||
document()
|
document()
|
||||||
.get_element_by_id("publish")
|
.get_element_by_id("publish")?
|
||||||
.ok_or(EditorError::NoneError)?
|
|
||||||
.add_event_listener_with_callback("click", show_popup.as_ref().unchecked_ref())
|
.add_event_listener_with_callback("click", show_popup.as_ref().unchecked_ref())
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
show_popup.forget();
|
show_popup.forget();
|
||||||
@@ -526,14 +528,8 @@ fn init_popup(
|
|||||||
cover_label
|
cover_label
|
||||||
.set_attribute("for", "cover")
|
.set_attribute("for", "cover")
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
let cover = document
|
let cover = document.get_element_by_id("cover")?;
|
||||||
.get_element_by_id("cover")
|
cover.parent_element()?.remove_child(&cover).ok();
|
||||||
.ok_or(EditorError::NoneError)?;
|
|
||||||
cover
|
|
||||||
.parent_element()
|
|
||||||
.ok_or(EditorError::NoneError)?
|
|
||||||
.remove_child(&cover)
|
|
||||||
.ok();
|
|
||||||
popup
|
popup
|
||||||
.append_child(&cover_label)
|
.append_child(&cover_label)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
@@ -558,7 +554,7 @@ fn init_popup(
|
|||||||
draft.set_checked(draft_checkbox.checked());
|
draft.set_checked(draft_checkbox.checked());
|
||||||
|
|
||||||
draft_label
|
draft_label
|
||||||
.append_child(draft)
|
.append_child(&draft)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
draft_label
|
draft_label
|
||||||
.append_child(&document.create_text_node(&i18n!(CATALOG, "This is a draft")))
|
.append_child(&document.create_text_node(&i18n!(CATALOG, "This is a draft")))
|
||||||
@@ -624,12 +620,11 @@ fn init_popup(
|
|||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
callback.forget();
|
callback.forget();
|
||||||
popup
|
popup
|
||||||
.append_child(button)
|
.append_child(&button)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
|
|
||||||
document
|
document
|
||||||
.body()
|
.body()?
|
||||||
.ok_or(EditorError::NoneError)?
|
|
||||||
.append_child(&popup)
|
.append_child(&popup)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
Ok(popup)
|
Ok(popup)
|
||||||
@@ -646,8 +641,7 @@ fn init_popup_bg() -> Result<Element, EditorError> {
|
|||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
|
|
||||||
document()
|
document()
|
||||||
.body()
|
.body()?
|
||||||
.ok_or(EditorError::NoneError)?
|
|
||||||
.append_child(&bg)
|
.append_child(&bg)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
let callback = Closure::wrap(Box::new(|_| close_popup()) as Box<dyn FnMut(MouseEvent)>);
|
let callback = Closure::wrap(Box::new(|_| close_popup()) as Box<dyn FnMut(MouseEvent)>);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#![recursion_limit = "128"]
|
#![recursion_limit = "128"]
|
||||||
#![feature(decl_macro, proc_macro_hygiene)]
|
#![feature(decl_macro, proc_macro_hygiene, try_trait)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate gettext_macros;
|
extern crate gettext_macros;
|
||||||
@@ -61,7 +61,7 @@ lazy_static! {
|
|||||||
static ref CATALOG: gettext::Catalog = {
|
static ref CATALOG: gettext::Catalog = {
|
||||||
let catalogs = include_i18n!();
|
let catalogs = include_i18n!();
|
||||||
let lang = window().unwrap().navigator().language().unwrap();
|
let lang = window().unwrap().navigator().language().unwrap();
|
||||||
let lang = lang.split_once('-').map_or("en", |x| x.0);
|
let lang = lang.splitn(2, '-').next().unwrap_or("en");
|
||||||
|
|
||||||
let english_position = catalogs
|
let english_position = catalogs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -85,7 +85,7 @@ pub fn main() -> Result<(), JsValue> {
|
|||||||
menu();
|
menu();
|
||||||
search();
|
search();
|
||||||
editor::init()
|
editor::init()
|
||||||
.map_err(|e| console::error_1(&format!("Editor error: {:?}", e).into()))
|
.map_err(|e| console::error_1(&&format!("Editor error: {:?}", e).into()))
|
||||||
.ok();
|
.ok();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-macro"
|
name = "plume-macro"
|
||||||
version = "0.7.1"
|
version = "0.6.1-dev"
|
||||||
authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"]
|
authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Plume procedural macros"
|
description = "Plume procedural macros"
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ pub fn import_migrations(input: TokenStream) -> TokenStream {
|
|||||||
(name, up_sql, down_sql)
|
(name, up_sql, down_sql)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let migrations_name = migrations.iter().map(|m| &m.0);
|
let migrations_name = migrations.iter().map(|m| &m.0).collect::<Vec<_>>();
|
||||||
let migrations_up = migrations
|
let migrations_up = migrations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| m.1.as_str())
|
.map(|m| m.1.as_str())
|
||||||
@@ -103,7 +103,7 @@ fn file_to_migration(file: &str) -> TokenStream2 {
|
|||||||
acc.push('\n');
|
acc.push('\n');
|
||||||
}
|
}
|
||||||
} else if let Some(acc_str) = line.strip_prefix("--#!") {
|
} else if let Some(acc_str) = line.strip_prefix("--#!") {
|
||||||
acc.push_str(acc_str);
|
acc.push_str(&acc_str);
|
||||||
acc.push('\n');
|
acc.push('\n');
|
||||||
} else if line.starts_with("--") {
|
} else if line.starts_with("--") {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
+10
-11
@@ -1,40 +1,39 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plume-models"
|
name = "plume-models"
|
||||||
version = "0.7.1"
|
version = "0.6.1-dev"
|
||||||
authors = ["Plume contributors"]
|
authors = ["Plume contributors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
activitypub = "0.1.1"
|
activitypub = "0.1.1"
|
||||||
ammonia = "3.1.2"
|
ammonia = "2.1.1"
|
||||||
|
askama_escape = "0.1"
|
||||||
bcrypt = "0.10.1"
|
bcrypt = "0.10.1"
|
||||||
guid-create = "0.2"
|
guid-create = "0.1"
|
||||||
itertools = "0.10.3"
|
itertools = "0.8.0"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
ldap3 = "0.9.3"
|
ldap3 = "0.7.1"
|
||||||
migrations_internals= "1.4.0"
|
migrations_internals= "1.4.0"
|
||||||
openssl = "0.10.22"
|
openssl = "0.10.22"
|
||||||
rocket = "0.4.6"
|
rocket = "=0.4.6"
|
||||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
||||||
reqwest = "0.9"
|
reqwest = "0.9"
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0.70"
|
serde_json = "1.0"
|
||||||
tantivy = "0.13.3"
|
tantivy = "0.13.3"
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
walkdir = "2.2"
|
walkdir = "2.2"
|
||||||
webfinger = "0.4.1"
|
webfinger = "0.4.1"
|
||||||
whatlang = "0.13.0"
|
whatlang = "0.11.1"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.2.1"
|
||||||
diesel-derive-newtype = "0.1.2"
|
diesel-derive-newtype = "0.1.2"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
lindera-tantivy = { version = "0.7.1", optional = true }
|
lindera-tantivy = { version = "0.7.1", optional = true }
|
||||||
tracing = "0.1.22"
|
tracing = "0.1.22"
|
||||||
riker = "0.4.2"
|
riker = "0.4.2"
|
||||||
once_cell = "1.5.2"
|
once_cell = "1.5.2"
|
||||||
lettre = "0.9.6"
|
|
||||||
native-tls = "0.2.8"
|
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|||||||
@@ -86,18 +86,14 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut parsed_header = headers[0].split(' ');
|
let mut parsed_header = headers[0].split(' ');
|
||||||
let auth_type = parsed_header
|
let auth_type = parsed_header.next().map_or_else(
|
||||||
.next()
|
|| Outcome::Failure((Status::BadRequest, TokenError::NoType)),
|
||||||
.map_or_else::<rocket::Outcome<&str, _, ()>, _, _>(
|
Outcome::Success,
|
||||||
|| Outcome::Failure((Status::BadRequest, TokenError::NoType)),
|
)?;
|
||||||
Outcome::Success,
|
let val = parsed_header.next().map_or_else(
|
||||||
)?;
|
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)),
|
||||||
let val = parsed_header
|
Outcome::Success,
|
||||||
.next()
|
)?;
|
||||||
.map_or_else::<rocket::Outcome<&str, _, ()>, _, _>(
|
|
||||||
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)),
|
|
||||||
Outcome::Success,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if auth_type == "Bearer" {
|
if auth_type == "Bearer" {
|
||||||
let conn = request
|
let conn = request
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl BlocklistedEmail {
|
|||||||
pub fn delete_entries(conn: &Connection, ids: Vec<i32>) -> Result<bool> {
|
pub fn delete_entries(conn: &Connection, ids: Vec<i32>) -> Result<bool> {
|
||||||
use diesel::delete;
|
use diesel::delete;
|
||||||
for i in ids {
|
for i in ids {
|
||||||
let be: BlocklistedEmail = BlocklistedEmail::find_by_id(conn, i)?;
|
let be: BlocklistedEmail = BlocklistedEmail::find_by_id(&conn, i)?;
|
||||||
delete(&be).execute(conn)?;
|
delete(&be).execute(conn)?;
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
|||||||
+32
-21
@@ -18,7 +18,8 @@ use openssl::{
|
|||||||
};
|
};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{AsActor, FromId},
|
inbox::{AsActor, FromId},
|
||||||
sign, ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
|
sign::{self, Error as SignatureError, Result as SignatureResult},
|
||||||
|
ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
|
||||||
};
|
};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use webfinger::*;
|
use webfinger::*;
|
||||||
@@ -152,7 +153,8 @@ impl Blog {
|
|||||||
.and_then(|l| {
|
.and_then(|l| {
|
||||||
Blog::from_id(
|
Blog::from_id(
|
||||||
conn,
|
conn,
|
||||||
&l.href.ok_or(Error::MissingApProperty)?,
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&l.href?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
)
|
)
|
||||||
@@ -244,7 +246,7 @@ impl Blog {
|
|||||||
(min, max): (i32, i32),
|
(min, max): (i32, i32),
|
||||||
) -> Result<ActivityStream<OrderedCollectionPage>> {
|
) -> Result<ActivityStream<OrderedCollectionPage>> {
|
||||||
let mut coll = OrderedCollectionPage::default();
|
let mut coll = OrderedCollectionPage::default();
|
||||||
let acts = self.get_activity_page(conn, (min, max));
|
let acts = self.get_activity_page(&conn, (min, max));
|
||||||
//This still doesn't do anything because the outbox
|
//This still doesn't do anything because the outbox
|
||||||
//doesn't do anything yet
|
//doesn't do anything yet
|
||||||
coll.collection_page_props.set_next_link(Id::new(&format!(
|
coll.collection_page_props.set_next_link(Id::new(&format!(
|
||||||
@@ -273,10 +275,7 @@ impl Blog {
|
|||||||
|
|
||||||
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
||||||
PKey::from_rsa(Rsa::private_key_from_pem(
|
PKey::from_rsa(Rsa::private_key_from_pem(
|
||||||
self.private_key
|
self.private_key.clone()?.as_ref(),
|
||||||
.clone()
|
|
||||||
.ok_or(Error::MissingApProperty)?
|
|
||||||
.as_ref(),
|
|
||||||
)?)
|
)?)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
@@ -329,7 +328,7 @@ impl Blog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||||
for post in Post::get_for_blog(conn, self)? {
|
for post in Post::get_for_blog(conn, &self)? {
|
||||||
post.delete(conn)?;
|
post.delete(conn)?;
|
||||||
}
|
}
|
||||||
diesel::delete(self)
|
diesel::delete(self)
|
||||||
@@ -350,12 +349,12 @@ impl FromId<DbConn> for Blog {
|
|||||||
type Object = CustomGroup;
|
type Object = CustomGroup;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(conn, id)
|
Self::find_by_ap_url(&conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> {
|
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> {
|
||||||
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
||||||
let inst = url.host_str().ok_or(Error::Url)?;
|
let inst = url.host_str()?;
|
||||||
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
|
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
|
||||||
Instance::insert(
|
Instance::insert(
|
||||||
conn,
|
conn,
|
||||||
@@ -370,6 +369,8 @@ impl FromId<DbConn> for Blog {
|
|||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
short_description_html: String::new(),
|
short_description_html: String::new(),
|
||||||
long_description_html: String::new(),
|
long_description_html: String::new(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@@ -383,7 +384,14 @@ impl FromId<DbConn> for Blog {
|
|||||||
Media::save_remote(
|
Media::save_remote(
|
||||||
conn,
|
conn,
|
||||||
icon.object_props.url_string().ok()?,
|
icon.object_props.url_string().ok()?,
|
||||||
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
|
&User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&owner,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.ok()?,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
@@ -399,7 +407,14 @@ impl FromId<DbConn> for Blog {
|
|||||||
Media::save_remote(
|
Media::save_remote(
|
||||||
conn,
|
conn,
|
||||||
banner.object_props.url_string().ok()?,
|
banner.object_props.url_string().ok()?,
|
||||||
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
|
&User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&owner,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.ok()?,
|
||||||
)
|
)
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
@@ -443,10 +458,6 @@ impl FromId<DbConn> for Blog {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn sign::Signer {
|
|
||||||
Instance::get_local_instance_user().expect("Failed to local instance user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&PlumeRocket> for Blog {
|
impl AsActor<&PlumeRocket> for Blog {
|
||||||
@@ -470,18 +481,18 @@ impl sign::Signer for Blog {
|
|||||||
format!("{}#main-key", self.ap_url)
|
format!("{}#main-key", self.ap_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> sign::Result<Vec<u8>> {
|
fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
|
||||||
let key = self.get_keypair().map_err(|_| sign::Error())?;
|
let key = self.get_keypair()?;
|
||||||
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
|
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
|
||||||
signer.update(to_sign.as_bytes())?;
|
signer.update(to_sign.as_bytes())?;
|
||||||
signer.sign_to_vec().map_err(sign::Error::from)
|
signer.sign_to_vec().map_err(SignatureError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> sign::Result<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
||||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
|
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
|
||||||
verifier.update(data.as_bytes())?;
|
verifier.update(data.as_bytes())?;
|
||||||
verifier.verify(signature).map_err(sign::Error::from)
|
verifier.verify(&signature).map_err(SignatureError::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
|||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
sign::Signer,
|
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
@@ -142,20 +141,18 @@ impl Comment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_activity(&self, conn: &DbConn) -> Result<Create> {
|
pub fn create_activity(&self, conn: &DbConn) -> Result<Create> {
|
||||||
let author = User::get(conn, self.author_id)?;
|
let author = User::get(&conn, self.author_id)?;
|
||||||
|
|
||||||
let note = self.to_activity(conn)?;
|
let note = self.to_activity(conn)?;
|
||||||
let mut act = Create::default();
|
let mut act = Create::default();
|
||||||
act.create_props.set_actor_link(author.into_id())?;
|
act.create_props.set_actor_link(author.into_id())?;
|
||||||
act.create_props.set_object_object(note.clone())?;
|
act.create_props.set_object_object(note.clone())?;
|
||||||
act.object_props.set_id_string(format!(
|
act.object_props
|
||||||
"{}/activity",
|
.set_id_string(format!("{}/activity", self.ap_url.clone()?,))?;
|
||||||
self.ap_url.clone().ok_or(Error::MissingApProperty)?,
|
|
||||||
))?;
|
|
||||||
act.object_props
|
act.object_props
|
||||||
.set_to_link_vec(note.object_props.to_link_vec::<Id>()?)?;
|
.set_to_link_vec(note.object_props.to_link_vec::<Id>()?)?;
|
||||||
act.object_props
|
act.object_props
|
||||||
.set_cc_link_vec(vec![Id::new(self.get_author(conn)?.followers_endpoint)])?;
|
.set_cc_link_vec(vec![Id::new(self.get_author(&conn)?.followers_endpoint)])?;
|
||||||
Ok(act)
|
Ok(act)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,9 +182,7 @@ impl Comment {
|
|||||||
.set_actor_link(self.get_author(conn)?.into_id())?;
|
.set_actor_link(self.get_author(conn)?.into_id())?;
|
||||||
|
|
||||||
let mut tombstone = Tombstone::default();
|
let mut tombstone = Tombstone::default();
|
||||||
tombstone
|
tombstone.object_props.set_id_string(self.ap_url.clone()?)?;
|
||||||
.object_props
|
|
||||||
.set_id_string(self.ap_url.clone().ok_or(Error::MissingApProperty)?)?;
|
|
||||||
act.delete_props.set_object_object(tombstone)?;
|
act.delete_props.set_object_object(tombstone)?;
|
||||||
|
|
||||||
act.object_props
|
act.object_props
|
||||||
@@ -209,13 +204,7 @@ impl FromId<DbConn> for Comment {
|
|||||||
|
|
||||||
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> {
|
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> {
|
||||||
let comm = {
|
let comm = {
|
||||||
let previous_url = note
|
let previous_url = note.object_props.in_reply_to.as_ref()?.as_str()?;
|
||||||
.object_props
|
|
||||||
.in_reply_to
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(Error::MissingApProperty)?
|
|
||||||
.as_str()
|
|
||||||
.ok_or(Error::MissingApProperty)?;
|
|
||||||
let previous_comment = Comment::find_by_ap_url(conn, previous_url);
|
let previous_comment = Comment::find_by_ap_url(conn, previous_url);
|
||||||
|
|
||||||
let is_public = |v: &Option<serde_json::Value>| match v
|
let is_public = |v: &Option<serde_json::Value>| match v
|
||||||
@@ -247,6 +236,7 @@ impl FromId<DbConn> for Comment {
|
|||||||
})?,
|
})?,
|
||||||
author_id: User::from_id(
|
author_id: User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
¬e.object_props.attributed_to_link::<Id>()?,
|
¬e.object_props.attributed_to_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@@ -305,7 +295,13 @@ impl FromId<DbConn> for Comment {
|
|||||||
.collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
|
.collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if let Ok(user) = User::from_id(conn, &v, None, CONFIG.proxy()) {
|
if let Ok(user) = User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&v,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
) {
|
||||||
vec![user]
|
vec![user]
|
||||||
} else {
|
} else {
|
||||||
vec![] // TODO try to fetch collection
|
vec![] // TODO try to fetch collection
|
||||||
@@ -329,10 +325,6 @@ impl FromId<DbConn> for Comment {
|
|||||||
comm.notify(conn)?;
|
comm.notify(conn)?;
|
||||||
Ok(comm)
|
Ok(comm)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
Instance::get_local_instance_user().expect("Failed to local instance user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &DbConn> for Comment {
|
impl AsObject<User, Create, &DbConn> for Comment {
|
||||||
@@ -361,7 +353,7 @@ impl AsObject<User, Delete, &DbConn> for Comment {
|
|||||||
m.delete(conn)?;
|
m.delete(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for n in Notification::find_for_comment(conn, &self)? {
|
for n in Notification::find_for_comment(&conn, &self)? {
|
||||||
n.delete(&**conn)?;
|
n.delete(&**conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-42
@@ -1,6 +1,4 @@
|
|||||||
use crate::search::TokenizerKind as SearchTokenizer;
|
use crate::search::TokenizerKind as SearchTokenizer;
|
||||||
use crate::signups::Strategy as SignupStrategy;
|
|
||||||
use crate::smtp::{SMTP_PORT, SUBMISSIONS_PORT, SUBMISSION_PORT};
|
|
||||||
use rocket::config::Limits;
|
use rocket::config::Limits;
|
||||||
use rocket::Config as RocketConfig;
|
use rocket::Config as RocketConfig;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -17,14 +15,12 @@ pub struct Config {
|
|||||||
pub db_name: &'static str,
|
pub db_name: &'static str,
|
||||||
pub db_max_size: Option<u32>,
|
pub db_max_size: Option<u32>,
|
||||||
pub db_min_idle: Option<u32>,
|
pub db_min_idle: Option<u32>,
|
||||||
pub signup: SignupStrategy,
|
|
||||||
pub search_index: String,
|
pub search_index: String,
|
||||||
pub search_tokenizers: SearchTokenizerConfig,
|
pub search_tokenizers: SearchTokenizerConfig,
|
||||||
pub rocket: Result<RocketConfig, InvalidRocketConfig>,
|
pub rocket: Result<RocketConfig, RocketError>,
|
||||||
pub logo: LogoConfig,
|
pub logo: LogoConfig,
|
||||||
pub default_theme: String,
|
pub default_theme: String,
|
||||||
pub media_directory: String,
|
pub media_directory: String,
|
||||||
pub mail: Option<MailConfig>,
|
|
||||||
pub ldap: Option<LdapConfig>,
|
pub ldap: Option<LdapConfig>,
|
||||||
pub proxy: Option<ProxyConfig>,
|
pub proxy: Option<ProxyConfig>,
|
||||||
}
|
}
|
||||||
@@ -35,21 +31,21 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum InvalidRocketConfig {
|
pub enum RocketError {
|
||||||
Env,
|
InvalidEnv,
|
||||||
Address,
|
InvalidAddress,
|
||||||
SecretKey,
|
InvalidSecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rocket_config() -> Result<RocketConfig, InvalidRocketConfig> {
|
fn get_rocket_config() -> Result<RocketConfig, RocketError> {
|
||||||
let mut c = RocketConfig::active().map_err(|_| InvalidRocketConfig::Env)?;
|
let mut c = RocketConfig::active().map_err(|_| RocketError::InvalidEnv)?;
|
||||||
|
|
||||||
let address = var("ROCKET_ADDRESS").unwrap_or_else(|_| "localhost".to_owned());
|
let address = var("ROCKET_ADDRESS").unwrap_or_else(|_| "localhost".to_owned());
|
||||||
let port = var("ROCKET_PORT")
|
let port = var("ROCKET_PORT")
|
||||||
.ok()
|
.ok()
|
||||||
.map(|s| s.parse::<u16>().unwrap())
|
.map(|s| s.parse::<u16>().unwrap())
|
||||||
.unwrap_or(7878);
|
.unwrap_or(7878);
|
||||||
let secret_key = var("ROCKET_SECRET_KEY").map_err(|_| InvalidRocketConfig::SecretKey)?;
|
let secret_key = var("ROCKET_SECRET_KEY").map_err(|_| RocketError::InvalidSecretKey)?;
|
||||||
let form_size = var("FORM_SIZE")
|
let form_size = var("FORM_SIZE")
|
||||||
.unwrap_or_else(|_| "128".to_owned())
|
.unwrap_or_else(|_| "128".to_owned())
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
@@ -60,10 +56,10 @@ fn get_rocket_config() -> Result<RocketConfig, InvalidRocketConfig> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
c.set_address(address)
|
c.set_address(address)
|
||||||
.map_err(|_| InvalidRocketConfig::Address)?;
|
.map_err(|_| RocketError::InvalidAddress)?;
|
||||||
c.set_port(port);
|
c.set_port(port);
|
||||||
c.set_secret_key(secret_key)
|
c.set_secret_key(secret_key)
|
||||||
.map_err(|_| InvalidRocketConfig::SecretKey)?;
|
.map_err(|_| RocketError::InvalidSecretKey)?;
|
||||||
|
|
||||||
c.set_limits(
|
c.set_limits(
|
||||||
Limits::new()
|
Limits::new()
|
||||||
@@ -159,7 +155,7 @@ impl Default for LogoConfig {
|
|||||||
.ok()
|
.ok()
|
||||||
.or_else(|| custom_main.clone());
|
.or_else(|| custom_main.clone());
|
||||||
let other = if let Some(main) = custom_main.clone() {
|
let other = if let Some(main) = custom_main.clone() {
|
||||||
let ext = |path: &str| match path.rsplit_once('.').map(|x| x.1) {
|
let ext = |path: &str| match path.rsplitn(2, '.').next() {
|
||||||
Some("png") => Some("image/png".to_owned()),
|
Some("png") => Some("image/png".to_owned()),
|
||||||
Some("jpg") | Some("jpeg") => Some("image/jpeg".to_owned()),
|
Some("jpg") | Some("jpeg") => Some("image/jpeg".to_owned()),
|
||||||
Some("svg") => Some("image/svg+xml".to_owned()),
|
Some("svg") => Some("image/svg+xml".to_owned()),
|
||||||
@@ -249,31 +245,6 @@ impl SearchTokenizerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MailConfig {
|
|
||||||
pub server: String,
|
|
||||||
pub port: u16,
|
|
||||||
pub helo_name: String,
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_mail_config() -> Option<MailConfig> {
|
|
||||||
Some(MailConfig {
|
|
||||||
server: env::var("MAIL_SERVER").ok()?,
|
|
||||||
port: env::var("MAIL_PORT").map_or(SUBMISSIONS_PORT, |port| match port.as_str() {
|
|
||||||
"smtp" => SMTP_PORT,
|
|
||||||
"submissions" => SUBMISSIONS_PORT,
|
|
||||||
"submission" => SUBMISSION_PORT,
|
|
||||||
number => number
|
|
||||||
.parse()
|
|
||||||
.expect(r#"MAIL_PORT must be "smtp", "submissions", "submission" or an integer."#),
|
|
||||||
}),
|
|
||||||
helo_name: env::var("MAIL_HELO_NAME").unwrap_or_else(|_| "localhost".to_owned()),
|
|
||||||
username: env::var("MAIL_USER").ok()?,
|
|
||||||
password: env::var("MAIL_PASSWORD").ok()?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LdapConfig {
|
pub struct LdapConfig {
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
pub base_dn: String,
|
pub base_dn: String,
|
||||||
@@ -364,7 +335,6 @@ lazy_static! {
|
|||||||
s.parse::<u32>()
|
s.parse::<u32>()
|
||||||
.expect("Couldn't parse DB_MIN_IDLE into u32")
|
.expect("Couldn't parse DB_MIN_IDLE into u32")
|
||||||
)),
|
)),
|
||||||
signup: var("SIGNUP").map_or(SignupStrategy::default(), |s| s.parse().unwrap()),
|
|
||||||
#[cfg(feature = "postgres")]
|
#[cfg(feature = "postgres")]
|
||||||
database_url: var("DATABASE_URL")
|
database_url: var("DATABASE_URL")
|
||||||
.unwrap_or_else(|_| format!("postgres://plume:plume@localhost/{}", DB_NAME)),
|
.unwrap_or_else(|_| format!("postgres://plume:plume@localhost/{}", DB_NAME)),
|
||||||
@@ -377,7 +347,6 @@ lazy_static! {
|
|||||||
default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()),
|
default_theme: var("DEFAULT_THEME").unwrap_or_else(|_| "default-light".to_owned()),
|
||||||
media_directory: var("MEDIA_UPLOAD_DIRECTORY")
|
media_directory: var("MEDIA_UPLOAD_DIRECTORY")
|
||||||
.unwrap_or_else(|_| "static/media".to_owned()),
|
.unwrap_or_else(|_| "static/media".to_owned()),
|
||||||
mail: get_mail_config(),
|
|
||||||
ldap: get_ldap_config(),
|
ldap: get_ldap_config(),
|
||||||
proxy: get_proxy_config(),
|
proxy: get_proxy_config(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
db_conn::DbConn,
|
|
||||||
schema::email_signups,
|
|
||||||
users::{NewUser, Role, User},
|
|
||||||
Error, Result,
|
|
||||||
};
|
|
||||||
use chrono::{offset::Utc, Duration, NaiveDateTime};
|
|
||||||
use diesel::{
|
|
||||||
Connection as _, ExpressionMethods, Identifiable, Insertable, QueryDsl, Queryable, RunQueryDsl,
|
|
||||||
};
|
|
||||||
use plume_common::utils::random_hex;
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
const TOKEN_VALIDITY_HOURS: i64 = 2;
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct Token(String);
|
|
||||||
|
|
||||||
impl From<String> for Token {
|
|
||||||
fn from(string: String) -> Self {
|
|
||||||
Token(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Token> for String {
|
|
||||||
fn from(token: Token) -> Self {
|
|
||||||
token.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Token {
|
|
||||||
type Target = String;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
|
||||||
fn generate() -> Self {
|
|
||||||
Self(random_hex())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable)]
|
|
||||||
pub struct EmailSignup {
|
|
||||||
pub id: i32,
|
|
||||||
pub email: String,
|
|
||||||
pub token: String,
|
|
||||||
pub expiration_date: NaiveDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable)]
|
|
||||||
#[table_name = "email_signups"]
|
|
||||||
pub struct NewEmailSignup<'a> {
|
|
||||||
pub email: &'a str,
|
|
||||||
pub token: &'a str,
|
|
||||||
pub expiration_date: NaiveDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EmailSignup {
|
|
||||||
pub fn start(conn: &DbConn, email: &str) -> Result<Token> {
|
|
||||||
conn.transaction(|| {
|
|
||||||
Self::ensure_user_not_exist_by_email(conn, email)?;
|
|
||||||
let _rows = Self::delete_existings_by_email(conn, email)?;
|
|
||||||
let token = Token::generate();
|
|
||||||
let expiration_date = Utc::now()
|
|
||||||
.naive_utc()
|
|
||||||
.checked_add_signed(Duration::hours(TOKEN_VALIDITY_HOURS))
|
|
||||||
.expect("could not calculate expiration date");
|
|
||||||
let new_signup = NewEmailSignup {
|
|
||||||
email,
|
|
||||||
token: &token,
|
|
||||||
expiration_date,
|
|
||||||
};
|
|
||||||
let _rows = diesel::insert_into(email_signups::table)
|
|
||||||
.values(new_signup)
|
|
||||||
.execute(&**conn)?;
|
|
||||||
|
|
||||||
Ok(token)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_by_token(conn: &DbConn, token: Token) -> Result<Self> {
|
|
||||||
let signup = email_signups::table
|
|
||||||
.filter(email_signups::token.eq(token.as_str()))
|
|
||||||
.first::<Self>(&**conn)
|
|
||||||
.map_err(Error::from)?;
|
|
||||||
Ok(signup)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn confirm(&self, conn: &DbConn) -> Result<()> {
|
|
||||||
conn.transaction(|| {
|
|
||||||
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
|
|
||||||
if self.expired() {
|
|
||||||
Self::delete_existings_by_email(conn, &self.email)?;
|
|
||||||
return Err(Error::Expired);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn complete(&self, conn: &DbConn, username: String, password: String) -> Result<User> {
|
|
||||||
conn.transaction(|| {
|
|
||||||
Self::ensure_user_not_exist_by_email(conn, &self.email)?;
|
|
||||||
let user = NewUser::new_local(
|
|
||||||
conn,
|
|
||||||
username,
|
|
||||||
"".to_string(),
|
|
||||||
Role::Normal,
|
|
||||||
"",
|
|
||||||
self.email.clone(),
|
|
||||||
Some(User::hash_pass(&password)?),
|
|
||||||
)?;
|
|
||||||
self.delete(conn)?;
|
|
||||||
Ok(user)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete(&self, conn: &DbConn) -> Result<()> {
|
|
||||||
let _rows = diesel::delete(self).execute(&**conn).map_err(Error::from)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_user_not_exist_by_email(conn: &DbConn, email: &str) -> Result<()> {
|
|
||||||
if User::email_used(conn, email)? {
|
|
||||||
let _rows = Self::delete_existings_by_email(conn, email)?;
|
|
||||||
return Err(Error::UserAlreadyExists);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_existings_by_email(conn: &DbConn, email: &str) -> Result<usize> {
|
|
||||||
let existing_signups = email_signups::table.filter(email_signups::email.eq(email));
|
|
||||||
diesel::delete(existing_signups)
|
|
||||||
.execute(&**conn)
|
|
||||||
.map_err(Error::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expired(&self) -> bool {
|
|
||||||
self.expiration_date < Utc::now().naive_utc()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -168,6 +168,7 @@ impl FromId<DbConn> for Follow {
|
|||||||
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> {
|
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> {
|
||||||
let actor = User::from_id(
|
let actor = User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&follow.follow_props.actor_link::<Id>()?,
|
&follow.follow_props.actor_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@@ -176,6 +177,7 @@ impl FromId<DbConn> for Follow {
|
|||||||
|
|
||||||
let target = User::from_id(
|
let target = User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&follow.follow_props.object_link::<Id>()?,
|
&follow.follow_props.object_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@@ -183,10 +185,6 @@ impl FromId<DbConn> for Follow {
|
|||||||
.map_err(|(_, e)| e)?;
|
.map_err(|(_, e)| e)?;
|
||||||
Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id)
|
Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
Instance::get_local_instance_user().expect("Failed to local instance user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &DbConn> for Follow {
|
impl AsObject<User, Undo, &DbConn> for Follow {
|
||||||
@@ -199,7 +197,7 @@ impl AsObject<User, Undo, &DbConn> for Follow {
|
|||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
|
if let Ok(notif) = Notification::find(&conn, notification_kind::FOLLOW, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
-15
@@ -3,7 +3,9 @@ use activitypub::activity::*;
|
|||||||
use crate::{
|
use crate::{
|
||||||
comments::Comment,
|
comments::Comment,
|
||||||
db_conn::DbConn,
|
db_conn::DbConn,
|
||||||
follows, likes,
|
follows,
|
||||||
|
instance::Instance,
|
||||||
|
likes,
|
||||||
posts::{Post, PostUpdate},
|
posts::{Post, PostUpdate},
|
||||||
reshares::Reshare,
|
reshares::Reshare,
|
||||||
users::User,
|
users::User,
|
||||||
@@ -47,20 +49,24 @@ impl_into_inbox_result! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
|
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
|
||||||
Inbox::handle(conn, act)
|
Inbox::handle(
|
||||||
.with::<User, Announce, Post>(CONFIG.proxy())
|
conn,
|
||||||
.with::<User, Create, Comment>(CONFIG.proxy())
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
.with::<User, Create, Post>(CONFIG.proxy())
|
act,
|
||||||
.with::<User, Delete, Comment>(CONFIG.proxy())
|
)
|
||||||
.with::<User, Delete, Post>(CONFIG.proxy())
|
.with::<User, Announce, Post>(CONFIG.proxy())
|
||||||
.with::<User, Delete, User>(CONFIG.proxy())
|
.with::<User, Create, Comment>(CONFIG.proxy())
|
||||||
.with::<User, Follow, User>(CONFIG.proxy())
|
.with::<User, Create, Post>(CONFIG.proxy())
|
||||||
.with::<User, Like, Post>(CONFIG.proxy())
|
.with::<User, Delete, Comment>(CONFIG.proxy())
|
||||||
.with::<User, Undo, Reshare>(CONFIG.proxy())
|
.with::<User, Delete, Post>(CONFIG.proxy())
|
||||||
.with::<User, Undo, follows::Follow>(CONFIG.proxy())
|
.with::<User, Delete, User>(CONFIG.proxy())
|
||||||
.with::<User, Undo, likes::Like>(CONFIG.proxy())
|
.with::<User, Follow, User>(CONFIG.proxy())
|
||||||
.with::<User, Update, PostUpdate>(CONFIG.proxy())
|
.with::<User, Like, Post>(CONFIG.proxy())
|
||||||
.done()
|
.with::<User, Undo, Reshare>(CONFIG.proxy())
|
||||||
|
.with::<User, Undo, follows::Follow>(CONFIG.proxy())
|
||||||
|
.with::<User, Undo, likes::Like>(CONFIG.proxy())
|
||||||
|
.with::<User, Update, PostUpdate>(CONFIG.proxy())
|
||||||
|
.done()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
+144
-44
@@ -3,14 +3,29 @@ use crate::{
|
|||||||
medias::Media,
|
medias::Media,
|
||||||
safe_string::SafeString,
|
safe_string::SafeString,
|
||||||
schema::{instances, users},
|
schema::{instances, users},
|
||||||
users::{NewUser, Role, User},
|
users::{Role, User},
|
||||||
Connection, Error, Result,
|
Connection, Error, Result,
|
||||||
};
|
};
|
||||||
|
use activitypub::{actor::Service, CustomObject};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use once_cell::sync::OnceCell;
|
use openssl::{
|
||||||
use plume_common::utils::md_to_html;
|
hash::MessageDigest,
|
||||||
|
pkey::{PKey, Private},
|
||||||
|
rsa::Rsa,
|
||||||
|
sign,
|
||||||
|
};
|
||||||
|
use plume_common::{
|
||||||
|
activity_pub::{
|
||||||
|
sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer},
|
||||||
|
ApSignature, PublicKey,
|
||||||
|
},
|
||||||
|
utils::md_to_html,
|
||||||
|
};
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
pub type CustomService = CustomObject<ApSignature, Service>;
|
||||||
|
|
||||||
#[derive(Clone, Identifiable, Queryable)]
|
#[derive(Clone, Identifiable, Queryable)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
@@ -26,6 +41,8 @@ pub struct Instance {
|
|||||||
pub default_license: String,
|
pub default_license: String,
|
||||||
pub long_description_html: SafeString,
|
pub long_description_html: SafeString,
|
||||||
pub short_description_html: SafeString,
|
pub short_description_html: SafeString,
|
||||||
|
pub private_key: Option<String>,
|
||||||
|
pub public_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Insertable)]
|
#[derive(Clone, Insertable)]
|
||||||
@@ -40,15 +57,14 @@ pub struct NewInstance {
|
|||||||
pub default_license: String,
|
pub default_license: String,
|
||||||
pub long_description_html: String,
|
pub long_description_html: String,
|
||||||
pub short_description_html: String,
|
pub short_description_html: String,
|
||||||
|
pub private_key: Option<String>,
|
||||||
|
pub public_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref LOCAL_INSTANCE: RwLock<Option<Instance>> = RwLock::new(None);
|
static ref LOCAL_INSTANCE: RwLock<Option<Instance>> = RwLock::new(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
const LOCAL_INSTANCE_USERNAME: &str = "__instance__";
|
|
||||||
static LOCAL_INSTANCE_USER: OnceCell<User> = OnceCell::new();
|
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub fn set_local(self) {
|
pub fn set_local(self) {
|
||||||
LOCAL_INSTANCE.write().unwrap().replace(self);
|
LOCAL_INSTANCE.write().unwrap().replace(self);
|
||||||
@@ -73,6 +89,13 @@ impl Instance {
|
|||||||
*LOCAL_INSTANCE.write().unwrap() = Instance::get_local_uncached(conn).ok();
|
*LOCAL_INSTANCE.write().unwrap() = Instance::get_local_uncached(conn).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_locals(conn: &Connection) -> Result<Vec<Instance>> {
|
||||||
|
instances::table
|
||||||
|
.filter(instances::local.eq(true))
|
||||||
|
.load::<Instance>(conn)
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_remotes(conn: &Connection) -> Result<Vec<Instance>> {
|
pub fn get_remotes(conn: &Connection) -> Result<Vec<Instance>> {
|
||||||
instances::table
|
instances::table
|
||||||
.filter(instances::local.eq(false))
|
.filter(instances::local.eq(false))
|
||||||
@@ -80,42 +103,6 @@ impl Instance {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_local_instance_user(conn: &Connection) -> Result<User> {
|
|
||||||
let instance = Instance::get_local()?;
|
|
||||||
let email = format!("{}@{}", LOCAL_INSTANCE_USERNAME, &instance.public_domain);
|
|
||||||
NewUser::new_local(
|
|
||||||
conn,
|
|
||||||
LOCAL_INSTANCE_USERNAME.into(),
|
|
||||||
instance.public_domain,
|
|
||||||
Role::Instance,
|
|
||||||
"Local instance",
|
|
||||||
email,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local_instance_user() -> Option<&'static User> {
|
|
||||||
LOCAL_INSTANCE_USER.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local_instance_user_uncached(conn: &Connection) -> Result<User> {
|
|
||||||
users::table
|
|
||||||
.filter(users::role.eq(3))
|
|
||||||
.first(conn)
|
|
||||||
.or_else(|err| match err {
|
|
||||||
NotFound => Self::create_local_instance_user(conn),
|
|
||||||
_ => Err(Error::Db(err)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cache_local_instance_user(conn: &Connection) {
|
|
||||||
let _ = LOCAL_INSTANCE_USER.get_or_init(|| {
|
|
||||||
Self::get_local_instance_user_uncached(conn)
|
|
||||||
.or_else(|_| Self::create_local_instance_user(conn))
|
|
||||||
.expect("Failed to cache local instance user")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<Instance>> {
|
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<Instance>> {
|
||||||
instances::table
|
instances::table
|
||||||
.order(instances::public_domain.asc())
|
.order(instances::public_domain.asc())
|
||||||
@@ -278,6 +265,112 @@ impl Instance {
|
|||||||
})
|
})
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_keypair(&self, conn: &Connection) -> Result<()> {
|
||||||
|
let (pub_key, priv_key) = gen_keypair();
|
||||||
|
let private_key = String::from_utf8(priv_key).or(Err(Error::Signature))?;
|
||||||
|
let public_key = String::from_utf8(pub_key).or(Err(Error::Signature))?;
|
||||||
|
diesel::update(self)
|
||||||
|
.set((
|
||||||
|
instances::private_key.eq(Some(private_key)),
|
||||||
|
instances::public_key.eq(Some(public_key)),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
.and(Ok(()))
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
||||||
|
PKey::from_rsa(Rsa::private_key_from_pem(
|
||||||
|
self.private_key.clone()?.as_ref(),
|
||||||
|
)?)
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is experimental and might change in the future.
|
||||||
|
/// Currently "!" sign is used but it's not decided.
|
||||||
|
pub fn ap_url(&self) -> String {
|
||||||
|
ap_url(&format!(
|
||||||
|
"{}/!/{}",
|
||||||
|
Self::get_local().unwrap().public_domain,
|
||||||
|
self.public_domain
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_activity(&self) -> Result<CustomService> {
|
||||||
|
let mut actor = Service::default();
|
||||||
|
let id = self.ap_url();
|
||||||
|
actor.object_props.set_id_string(id.clone())?;
|
||||||
|
actor.object_props.set_name_string(self.name.clone())?;
|
||||||
|
|
||||||
|
let mut ap_signature = ApSignature::default();
|
||||||
|
if self.local {
|
||||||
|
if let Some(pub_key) = self.public_key.clone() {
|
||||||
|
let mut public_key = PublicKey::default();
|
||||||
|
public_key.set_id_string(format!("{}#main-key", id))?;
|
||||||
|
public_key.set_owner_string(id)?;
|
||||||
|
public_key.set_public_key_pem_string(pub_key)?;
|
||||||
|
ap_signature.set_public_key_publickey(public_key)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(CustomService::new(actor, ap_signature))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewInstance {
|
||||||
|
pub fn new_local(
|
||||||
|
conn: &Connection,
|
||||||
|
public_domain: String,
|
||||||
|
name: String,
|
||||||
|
open_registrations: bool,
|
||||||
|
default_license: String,
|
||||||
|
) -> Result<Instance> {
|
||||||
|
let (pub_key, priv_key) = gen_keypair();
|
||||||
|
|
||||||
|
Instance::insert(
|
||||||
|
conn,
|
||||||
|
NewInstance {
|
||||||
|
public_domain,
|
||||||
|
name,
|
||||||
|
local: true,
|
||||||
|
open_registrations,
|
||||||
|
short_description: SafeString::new(""),
|
||||||
|
long_description: SafeString::new(""),
|
||||||
|
default_license,
|
||||||
|
long_description_html: String::new(),
|
||||||
|
short_description_html: String::new(),
|
||||||
|
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
|
||||||
|
public_key: Some(String::from_utf8(pub_key).or(Err(Error::Signature))?),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signer for Instance {
|
||||||
|
fn get_key_id(&self) -> String {
|
||||||
|
format!("{}#main-key", self.ap_url())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
|
||||||
|
let key = self.get_keypair()?;
|
||||||
|
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
|
||||||
|
signer.update(to_sign.as_bytes())?;
|
||||||
|
signer.sign_to_vec().map_err(SignatureError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
|
||||||
|
if self.public_key.is_none() {
|
||||||
|
warn!("missing public key for {}", self.public_domain);
|
||||||
|
return Err(SignatureError());
|
||||||
|
}
|
||||||
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(
|
||||||
|
self.public_key.clone().unwrap().as_ref(),
|
||||||
|
)?)?;
|
||||||
|
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
|
||||||
|
verifier.update(data.as_bytes())?;
|
||||||
|
verifier.verify(&signature).map_err(SignatureError::from)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -299,6 +392,8 @@ pub(crate) mod tests {
|
|||||||
name: "My instance".to_string(),
|
name: "My instance".to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: "plu.me".to_string(),
|
public_domain: "plu.me".to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
NewInstance {
|
NewInstance {
|
||||||
default_license: "WTFPL".to_string(),
|
default_license: "WTFPL".to_string(),
|
||||||
@@ -310,6 +405,8 @@ pub(crate) mod tests {
|
|||||||
name: "An instance".to_string(),
|
name: "An instance".to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: "1plu.me".to_string(),
|
public_domain: "1plu.me".to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
NewInstance {
|
NewInstance {
|
||||||
default_license: "CC-0".to_string(),
|
default_license: "CC-0".to_string(),
|
||||||
@@ -321,6 +418,8 @@ pub(crate) mod tests {
|
|||||||
name: "Someone instance".to_string(),
|
name: "Someone instance".to_string(),
|
||||||
open_registrations: false,
|
open_registrations: false,
|
||||||
public_domain: "2plu.me".to_string(),
|
public_domain: "2plu.me".to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
NewInstance {
|
NewInstance {
|
||||||
default_license: "CC-0-BY-SA".to_string(),
|
default_license: "CC-0-BY-SA".to_string(),
|
||||||
@@ -332,6 +431,8 @@ pub(crate) mod tests {
|
|||||||
name: "Nice day".to_string(),
|
name: "Nice day".to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: "3plu.me".to_string(),
|
public_domain: "3plu.me".to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -344,7 +445,6 @@ pub(crate) mod tests {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Instance::cache_local(conn);
|
Instance::cache_local(conn);
|
||||||
Instance::cache_local_instance_user(conn);
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Regular → Executable
+21
-33
@@ -1,3 +1,4 @@
|
|||||||
|
#![feature(try_trait)]
|
||||||
#![feature(never_type)]
|
#![feature(never_type)]
|
||||||
#![feature(proc_macro_hygiene)]
|
#![feature(proc_macro_hygiene)]
|
||||||
#![feature(box_patterns)]
|
#![feature(box_patterns)]
|
||||||
@@ -16,10 +17,10 @@ extern crate serde_json;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tantivy;
|
extern crate tantivy;
|
||||||
|
|
||||||
pub use lettre;
|
use db_conn::DbPool;
|
||||||
pub use lettre::smtp;
|
use instance::Instance;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use plume_common::activity_pub::{inbox::InboxError, request, sign};
|
use plume_common::activity_pub::{inbox::InboxError, sign};
|
||||||
use posts::PostEvent;
|
use posts::PostEvent;
|
||||||
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
|
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
|
||||||
use users::UserEvent;
|
use users::UserEvent;
|
||||||
@@ -67,7 +68,6 @@ pub enum Error {
|
|||||||
Url,
|
Url,
|
||||||
Webfinger,
|
Webfinger,
|
||||||
Expired,
|
Expired,
|
||||||
UserAlreadyExists,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<bcrypt::BcryptError> for Error {
|
impl From<bcrypt::BcryptError> for Error {
|
||||||
@@ -94,6 +94,12 @@ impl From<diesel::result::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::option::NoneError> for Error {
|
||||||
|
fn from(_: std::option::NoneError) -> Self {
|
||||||
|
Error::NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<url::ParseError> for Error {
|
impl From<url::ParseError> for Error {
|
||||||
fn from(_: url::ParseError) -> Self {
|
fn from(_: url::ParseError) -> Self {
|
||||||
Error::Url
|
Error::Url
|
||||||
@@ -160,14 +166,14 @@ impl From<InboxError<Error>> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<request::Error> for Error {
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
fn from(_err: request::Error) -> Error {
|
|
||||||
Error::Request
|
impl From<Error> for sign::Error {
|
||||||
|
fn from(_: Error) -> Self {
|
||||||
|
Self()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Adds a function to a model, that returns the first
|
/// Adds a function to a model, that returns the first
|
||||||
/// matching row for a given list of fields.
|
/// matching row for a given list of fields.
|
||||||
///
|
///
|
||||||
@@ -303,31 +309,15 @@ pub fn ap_url(url: &str) -> String {
|
|||||||
format!("https://{}", url)
|
format!("https://{}", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SmtpNewWithAddr {
|
pub fn migrate_data(dbpool: &DbPool) -> Result<()> {
|
||||||
fn new_with_addr(
|
ensure_local_instance_keys(&dbpool.get().unwrap())
|
||||||
addr: (&str, u16),
|
|
||||||
) -> std::result::Result<smtp::SmtpClient, smtp::error::Error>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SmtpNewWithAddr for smtp::SmtpClient {
|
fn ensure_local_instance_keys(conn: &Connection) -> Result<()> {
|
||||||
// Stolen from lettre::smtp::SmtpClient::new_simple()
|
for instance in Instance::get_locals(conn)? {
|
||||||
fn new_with_addr(addr: (&str, u16)) -> std::result::Result<Self, smtp::error::Error> {
|
instance.set_keypair(conn)?;
|
||||||
use native_tls::TlsConnector;
|
|
||||||
use smtp::{
|
|
||||||
client::net::{ClientTlsParameters, DEFAULT_TLS_PROTOCOLS},
|
|
||||||
ClientSecurity, SmtpClient,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (domain, port) = addr;
|
|
||||||
|
|
||||||
let mut tls_builder = TlsConnector::builder();
|
|
||||||
tls_builder.min_protocol_version(Some(DEFAULT_TLS_PROTOCOLS[0]));
|
|
||||||
|
|
||||||
let tls_parameters =
|
|
||||||
ClientTlsParameters::new(domain.to_string(), tls_builder.build().unwrap());
|
|
||||||
|
|
||||||
SmtpClient::new((domain, port), ClientSecurity::Wrapper(tls_parameters))
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -377,7 +367,6 @@ pub mod blogs;
|
|||||||
pub mod comment_seers;
|
pub mod comment_seers;
|
||||||
pub mod comments;
|
pub mod comments;
|
||||||
pub mod db_conn;
|
pub mod db_conn;
|
||||||
pub mod email_signups;
|
|
||||||
pub mod follows;
|
pub mod follows;
|
||||||
pub mod headers;
|
pub mod headers;
|
||||||
pub mod inbox;
|
pub mod inbox;
|
||||||
@@ -398,7 +387,6 @@ pub mod safe_string;
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod signups;
|
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
pub mod timeline;
|
pub mod timeline;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use chrono::NaiveDateTime;
|
|||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
sign::Signer,
|
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,6 +117,7 @@ impl FromId<DbConn> for Like {
|
|||||||
NewLike {
|
NewLike {
|
||||||
post_id: Post::from_id(
|
post_id: Post::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&act.like_props.object_link::<Id>()?,
|
&act.like_props.object_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@@ -126,6 +126,7 @@ impl FromId<DbConn> for Like {
|
|||||||
.id,
|
.id,
|
||||||
user_id: User::from_id(
|
user_id: User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&act.like_props.actor_link::<Id>()?,
|
&act.like_props.actor_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@@ -138,10 +139,6 @@ impl FromId<DbConn> for Like {
|
|||||||
res.notify(conn)?;
|
res.notify(conn)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
Instance::get_local_instance_user().expect("Failed to local instance user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, activity::Undo, &DbConn> for Like {
|
impl AsObject<User, activity::Undo, &DbConn> for Like {
|
||||||
@@ -153,7 +150,7 @@ impl AsObject<User, activity::Undo, &DbConn> for Like {
|
|||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
|
if let Ok(notif) = Notification::find(&conn, notification_kind::LIKE, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ macro_rules! func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Clone, Queryable, Identifiable)]
|
#[derive(Clone, Queryable, Identifiable)]
|
||||||
struct ListElem {
|
struct ListElem {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
|||||||
+26
-30
@@ -3,11 +3,12 @@ use crate::{
|
|||||||
users::User, Connection, Error, Result, CONFIG,
|
users::User, Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitypub::object::Image;
|
use activitypub::object::Image;
|
||||||
|
use askama_escape::escape;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use guid_create::GUID;
|
use guid_create::GUID;
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{inbox::FromId, request, Id},
|
activity_pub::{inbox::FromId, Id},
|
||||||
utils::{escape, MediaProcessor},
|
utils::MediaProcessor,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self, DirBuilder},
|
fs::{self, DirBuilder},
|
||||||
@@ -103,8 +104,8 @@ impl Media {
|
|||||||
pub fn category(&self) -> MediaCategory {
|
pub fn category(&self) -> MediaCategory {
|
||||||
match &*self
|
match &*self
|
||||||
.file_path
|
.file_path
|
||||||
.rsplit_once('.')
|
.rsplitn(2, '.')
|
||||||
.map(|x| x.1)
|
.next()
|
||||||
.expect("Media::category: extension error")
|
.expect("Media::category: extension error")
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
{
|
{
|
||||||
@@ -207,33 +208,31 @@ impl Media {
|
|||||||
|
|
||||||
// TODO: merge with save_remote?
|
// TODO: merge with save_remote?
|
||||||
pub fn from_activity(conn: &DbConn, image: &Image) -> Result<Media> {
|
pub fn from_activity(conn: &DbConn, image: &Image) -> Result<Media> {
|
||||||
let remote_url = image
|
let remote_url = image.object_props.url_string().ok()?;
|
||||||
.object_props
|
|
||||||
.url_string()
|
|
||||||
.or(Err(Error::MissingApProperty))?;
|
|
||||||
let path = determine_mirror_file_path(&remote_url);
|
let path = determine_mirror_file_path(&remote_url);
|
||||||
let parent = path.parent().ok_or(Error::InvalidValue)?;
|
let parent = path.parent()?;
|
||||||
if !parent.is_dir() {
|
if !parent.is_dir() {
|
||||||
DirBuilder::new().recursive(true).create(parent)?;
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dest = fs::File::create(path.clone())?;
|
let mut dest = fs::File::create(path.clone()).ok()?;
|
||||||
// TODO: conditional GET
|
// TODO: conditional GET
|
||||||
request::get(
|
if let Some(proxy) = CONFIG.proxy() {
|
||||||
remote_url.as_str(),
|
reqwest::ClientBuilder::new().proxy(proxy.clone()).build()?
|
||||||
User::get_sender(),
|
} else {
|
||||||
CONFIG.proxy().cloned(),
|
reqwest::Client::new()
|
||||||
)?
|
}
|
||||||
.copy_to(&mut dest)?;
|
.get(remote_url.as_str())
|
||||||
|
.send()
|
||||||
|
.ok()?
|
||||||
|
.copy_to(&mut dest)
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
Media::find_by_file_path(conn, path.to_str().ok_or(Error::InvalidValue)?)
|
Media::find_by_file_path(conn, &path.to_str()?)
|
||||||
.and_then(|mut media| {
|
.and_then(|mut media| {
|
||||||
let mut updated = false;
|
let mut updated = false;
|
||||||
|
|
||||||
let alt_text = image
|
let alt_text = image.object_props.content_string().ok()?;
|
||||||
.object_props
|
|
||||||
.content_string()
|
|
||||||
.or(Err(Error::NotFound))?;
|
|
||||||
let sensitive = image.object_props.summary_string().is_ok();
|
let sensitive = image.object_props.summary_string().is_ok();
|
||||||
let content_warning = image.object_props.summary_string().ok();
|
let content_warning = image.object_props.summary_string().ok();
|
||||||
if media.alt_text != alt_text {
|
if media.alt_text != alt_text {
|
||||||
@@ -265,24 +264,21 @@ impl Media {
|
|||||||
Media::insert(
|
Media::insert(
|
||||||
conn,
|
conn,
|
||||||
NewMedia {
|
NewMedia {
|
||||||
file_path: path.to_str().ok_or(Error::InvalidValue)?.to_string(),
|
file_path: path.to_str()?.to_string(),
|
||||||
alt_text: image
|
alt_text: image.object_props.content_string().ok()?,
|
||||||
.object_props
|
|
||||||
.content_string()
|
|
||||||
.or(Err(Error::NotFound))?,
|
|
||||||
is_remote: false,
|
is_remote: false,
|
||||||
remote_url: None,
|
remote_url: None,
|
||||||
sensitive: image.object_props.summary_string().is_ok(),
|
sensitive: image.object_props.summary_string().is_ok(),
|
||||||
content_warning: image.object_props.summary_string().ok(),
|
content_warning: image.object_props.summary_string().ok(),
|
||||||
owner_id: User::from_id(
|
owner_id: User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
image
|
image
|
||||||
.object_props
|
.object_props
|
||||||
.attributed_to_link_vec::<Id>()
|
.attributed_to_link_vec::<Id>()
|
||||||
.or(Err(Error::NotFound))?
|
.ok()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()?
|
||||||
.ok_or(Error::NotFound)?
|
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@@ -330,7 +326,7 @@ fn determine_mirror_file_path(url: &str) -> PathBuf {
|
|||||||
.next()
|
.next()
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.unwrap_or_else(|| String::from("png"));
|
.unwrap_or_else(|| String::from("png"));
|
||||||
file_path.push(format!("{}.{}", GUID::rand(), ext));
|
file_path.push(format!("{}.{}", GUID::rand().to_string(), ext));
|
||||||
});
|
});
|
||||||
file_path
|
file_path
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,11 +47,7 @@ impl Mention {
|
|||||||
|
|
||||||
pub fn get_user(&self, conn: &Connection) -> Result<User> {
|
pub fn get_user(&self, conn: &Connection) -> Result<User> {
|
||||||
match self.get_post(conn) {
|
match self.get_post(conn) {
|
||||||
Ok(p) => Ok(p
|
Ok(p) => Ok(p.get_authors(conn)?.into_iter().next()?),
|
||||||
.get_authors(conn)?
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.ok_or(Error::NotFound)?),
|
|
||||||
Err(_) => self.get_comment(conn).and_then(|c| c.get_author(conn)),
|
Err(_) => self.get_comment(conn).and_then(|c| c.get_author(conn)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +77,7 @@ impl Mention {
|
|||||||
in_post: bool,
|
in_post: bool,
|
||||||
notify: bool,
|
notify: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let ap_url = ment.link_props.href_string().or(Err(Error::NotFound))?;
|
let ap_url = ment.link_props.href_string().ok()?;
|
||||||
let mentioned = User::find_by_ap_url(conn, &ap_url)?;
|
let mentioned = User::find_by_ap_url(conn, &ap_url)?;
|
||||||
|
|
||||||
if in_post {
|
if in_post {
|
||||||
|
|||||||
@@ -105,8 +105,7 @@ impl ImportedMigrations {
|
|||||||
pub fn rerun_last_migration(&self, conn: &Connection, path: &Path) -> Result<()> {
|
pub fn rerun_last_migration(&self, conn: &Connection, path: &Path) -> Result<()> {
|
||||||
let latest_migration = conn.latest_run_migration_version()?;
|
let latest_migration = conn.latest_run_migration_version()?;
|
||||||
let id = latest_migration
|
let id = latest_migration
|
||||||
.and_then(|m| self.0.binary_search_by_key(&m.as_str(), |m| m.name).ok())
|
.and_then(|m| self.0.binary_search_by_key(&m.as_str(), |m| m.name).ok())?;
|
||||||
.ok_or(Error::NotFound)?;
|
|
||||||
let migration = &self.0[id];
|
let migration = &self.0[id];
|
||||||
conn.transaction(|| {
|
conn.transaction(|| {
|
||||||
migration.revert(conn, path)?;
|
migration.revert(conn, path)?;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ impl PasswordResetRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_and_delete_by_token(conn: &Connection, token: &str) -> Result<Self> {
|
pub fn find_and_delete_by_token(conn: &Connection, token: &str) -> Result<Self> {
|
||||||
let request = Self::find_by_token(conn, token)?;
|
let request = Self::find_by_token(&conn, &token)?;
|
||||||
|
|
||||||
let filter =
|
let filter =
|
||||||
password_reset_requests::table.filter(password_reset_requests::id.eq(request.id));
|
password_reset_requests::table.filter(password_reset_requests::id.eq(request.id));
|
||||||
|
|||||||
+38
-26
@@ -10,12 +10,11 @@ use activitypub::{
|
|||||||
CustomObject,
|
CustomObject,
|
||||||
};
|
};
|
||||||
use chrono::{NaiveDateTime, TimeZone, Utc};
|
use chrono::{NaiveDateTime, TimeZone, Utc};
|
||||||
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
sign::Signer,
|
|
||||||
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY,
|
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils::{iri_percent_encode_seg, md_to_html},
|
utils::{iri_percent_encode_seg, md_to_html},
|
||||||
@@ -67,15 +66,15 @@ impl Post {
|
|||||||
find_by!(posts, find_by_ap_url, ap_url as &str);
|
find_by!(posts, find_by_ap_url, ap_url as &str);
|
||||||
|
|
||||||
last!(posts);
|
last!(posts);
|
||||||
pub fn insert(conn: &Connection, mut new: NewPost) -> Result<Self> {
|
pub fn insert(conn: &Connection, new: NewPost) -> Result<Self> {
|
||||||
if new.ap_url.is_empty() {
|
|
||||||
let blog = Blog::get(conn, new.blog_id)?;
|
|
||||||
new.ap_url = Self::ap_url(blog, &new.slug);
|
|
||||||
}
|
|
||||||
diesel::insert_into(posts::table)
|
diesel::insert_into(posts::table)
|
||||||
.values(new)
|
.values(new)
|
||||||
.execute(conn)?;
|
.execute(conn)?;
|
||||||
let post = Self::last(conn)?;
|
let mut post = Self::last(conn)?;
|
||||||
|
if post.ap_url.is_empty() {
|
||||||
|
post.ap_url = Self::ap_url(post.get_blog(conn)?, &post.slug);
|
||||||
|
let _: Post = post.save_changes(conn)?;
|
||||||
|
}
|
||||||
|
|
||||||
if post.published {
|
if post.published {
|
||||||
post.publish_published();
|
post.publish_published();
|
||||||
@@ -98,7 +97,7 @@ impl Post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||||
for m in Mention::list_for_post(conn, self.id)? {
|
for m in Mention::list_for_post(&conn, self.id)? {
|
||||||
m.delete(conn)?;
|
m.delete(conn)?;
|
||||||
}
|
}
|
||||||
diesel::delete(self).execute(conn)?;
|
diesel::delete(self).execute(conn)?;
|
||||||
@@ -458,14 +457,14 @@ impl Post {
|
|||||||
.filter_map(|(id, m)| id.map(|id| (m, id)))
|
.filter_map(|(id, m)| id.map(|id| (m, id)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let old_mentions = Mention::list_for_post(conn, self.id)?;
|
let old_mentions = Mention::list_for_post(&conn, self.id)?;
|
||||||
let old_user_mentioned = old_mentions
|
let old_user_mentioned = old_mentions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| m.mentioned_id)
|
.map(|m| m.mentioned_id)
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
for (m, id) in &mentions {
|
for (m, id) in &mentions {
|
||||||
if !old_user_mentioned.contains(id) {
|
if !old_user_mentioned.contains(&id) {
|
||||||
Mention::from_activity(&*conn, m, self.id, true, true)?;
|
Mention::from_activity(&*conn, &m, self.id, true, true)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,7 +476,7 @@ impl Post {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|m| !new_mentions.contains(&m.mentioned_id))
|
.filter(|m| !new_mentions.contains(&m.mentioned_id))
|
||||||
{
|
{
|
||||||
m.delete(conn)?;
|
m.delete(&conn)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -631,13 +630,28 @@ impl FromId<DbConn> for Post {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.fold((None, vec![]), |(blog, mut authors), link| {
|
.fold((None, vec![]), |(blog, mut authors), link| {
|
||||||
let url = link;
|
let url = link;
|
||||||
match User::from_id(conn, &url, None, CONFIG.proxy()) {
|
match User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&url,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
) {
|
||||||
Ok(u) => {
|
Ok(u) => {
|
||||||
authors.push(u);
|
authors.push(u);
|
||||||
(blog, authors)
|
(blog, authors)
|
||||||
}
|
}
|
||||||
Err(_) => (
|
Err(_) => (
|
||||||
blog.or_else(|| Blog::from_id(conn, &url, None, CONFIG.proxy()).ok()),
|
blog.or_else(|| {
|
||||||
|
Blog::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&url,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
}),
|
||||||
authors,
|
authors,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -701,7 +715,7 @@ impl FromId<DbConn> for Post {
|
|||||||
Post::insert(
|
Post::insert(
|
||||||
conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blog.ok_or(Error::NotFound)?.id,
|
blog_id: blog?.id,
|
||||||
slug: Self::slug(&title).to_string(),
|
slug: Self::slug(&title).to_string(),
|
||||||
title,
|
title,
|
||||||
content: SafeString::new(&article.object_props.content_string()?),
|
content: SafeString::new(&article.object_props.content_string()?),
|
||||||
@@ -760,10 +774,6 @@ impl FromId<DbConn> for Post {
|
|||||||
|
|
||||||
Ok(post)
|
Ok(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
Instance::get_local_instance_user().expect("Failed to local instance user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &DbConn> for Post {
|
impl AsObject<User, Create, &DbConn> for Post {
|
||||||
@@ -835,10 +845,6 @@ impl FromId<DbConn> for PostUpdate {
|
|||||||
tags: updated.object.object_props.tag,
|
tags: updated.object.object_props.tag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
Instance::get_local_instance_user().expect("Failed to local instance user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Update, &DbConn> for PostUpdate {
|
impl AsObject<User, Update, &DbConn> for PostUpdate {
|
||||||
@@ -846,8 +852,14 @@ impl AsObject<User, Update, &DbConn> for PostUpdate {
|
|||||||
type Output = ();
|
type Output = ();
|
||||||
|
|
||||||
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
|
||||||
let mut post =
|
let mut post = Post::from_id(
|
||||||
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?;
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&self.ap_url,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.map_err(|(_, e)| e)?;
|
||||||
|
|
||||||
if !post.is_author(conn, actor.id)? {
|
if !post.is_author(conn, actor.id)? {
|
||||||
// TODO: maybe the author was added in the meantime
|
// TODO: maybe the author was added in the meantime
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::{DbConn, DbPool},
|
db_conn::{DbConn, DbPool},
|
||||||
follows,
|
follows,
|
||||||
|
instance::Instance,
|
||||||
posts::{LicensedArticle, Post},
|
posts::{LicensedArticle, Post},
|
||||||
users::{User, UserEvent},
|
users::{User, UserEvent},
|
||||||
ACTOR_SYS, CONFIG, USER_CHAN,
|
ACTOR_SYS, CONFIG, USER_CHAN,
|
||||||
@@ -89,7 +90,13 @@ fn fetch_and_cache_followers(user: &Arc<User>, conn: &DbConn) {
|
|||||||
match follower_ids {
|
match follower_ids {
|
||||||
Ok(user_ids) => {
|
Ok(user_ids) => {
|
||||||
for user_id in user_ids {
|
for user_id in user_ids {
|
||||||
let follower = User::from_id(conn, &user_id, None, CONFIG.proxy());
|
let follower = User::from_id(
|
||||||
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
&user_id,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
);
|
||||||
match follower {
|
match follower {
|
||||||
Ok(follower) => {
|
Ok(follower) => {
|
||||||
let inserted = follows::Follow::insert(
|
let inserted = follows::Follow::insert(
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use chrono::NaiveDateTime;
|
|||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
sign::Signer,
|
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,6 +142,7 @@ impl FromId<DbConn> for Reshare {
|
|||||||
NewReshare {
|
NewReshare {
|
||||||
post_id: Post::from_id(
|
post_id: Post::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&act.announce_props.object_link::<Id>()?,
|
&act.announce_props.object_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@@ -151,6 +151,7 @@ impl FromId<DbConn> for Reshare {
|
|||||||
.id,
|
.id,
|
||||||
user_id: User::from_id(
|
user_id: User::from_id(
|
||||||
conn,
|
conn,
|
||||||
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
&act.announce_props.actor_link::<Id>()?,
|
&act.announce_props.actor_link::<Id>()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@@ -163,10 +164,6 @@ impl FromId<DbConn> for Reshare {
|
|||||||
res.notify(conn)?;
|
res.notify(conn)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
Instance::get_local_instance_user().expect("Failed to local instance user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &DbConn> for Reshare {
|
impl AsObject<User, Undo, &DbConn> for Reshare {
|
||||||
@@ -178,7 +175,7 @@ impl AsObject<User, Undo, &DbConn> for Reshare {
|
|||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
|
if let Ok(notif) = Notification::find(&conn, notification_kind::RESHARE, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ pub struct SafeString {
|
|||||||
impl SafeString {
|
impl SafeString {
|
||||||
pub fn new(value: &str) -> Self {
|
pub fn new(value: &str) -> Self {
|
||||||
SafeString {
|
SafeString {
|
||||||
value: CLEAN.clean(value).to_string(),
|
value: CLEAN.clean(&value).to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ table! {
|
|||||||
user_id -> Int4,
|
user_id -> Int4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
email_blocklist(id){
|
email_blocklist(id){
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
@@ -84,15 +83,6 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table! {
|
|
||||||
email_signups (id) {
|
|
||||||
id -> Int4,
|
|
||||||
email -> Varchar,
|
|
||||||
token -> Varchar,
|
|
||||||
expiration_date -> Timestamp,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
follows (id) {
|
follows (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
@@ -116,6 +106,8 @@ table! {
|
|||||||
default_license -> Text,
|
default_license -> Text,
|
||||||
long_description_html -> Varchar,
|
long_description_html -> Varchar,
|
||||||
short_description_html -> Varchar,
|
short_description_html -> Varchar,
|
||||||
|
private_key -> Nullable<Text>,
|
||||||
|
public_key -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +308,6 @@ allow_tables_to_appear_in_same_query!(
|
|||||||
blogs,
|
blogs,
|
||||||
comments,
|
comments,
|
||||||
comment_seers,
|
comment_seers,
|
||||||
email_signups,
|
|
||||||
follows,
|
follows,
|
||||||
instances,
|
instances,
|
||||||
likes,
|
likes,
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ mod tests {
|
|||||||
name: random_hex().to_string(),
|
name: random_hex().to_string(),
|
||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
public_domain: random_hex().to_string(),
|
public_domain: random_hex().to_string(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ impl PlumeQuery {
|
|||||||
|
|
||||||
/// Parse a query string into this Query
|
/// Parse a query string into this Query
|
||||||
pub fn parse_query(&mut self, query: &str) -> &mut Self {
|
pub fn parse_query(&mut self, query: &str) -> &mut Self {
|
||||||
self.from_str_req(query.trim())
|
self.from_str_req(&query.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this Query to a Tantivy Query
|
/// Convert this Query to a Tantivy Query
|
||||||
@@ -360,7 +360,7 @@ impl std::str::FromStr for PlumeQuery {
|
|||||||
fn from_str(query: &str) -> Result<PlumeQuery, !> {
|
fn from_str(query: &str) -> Result<PlumeQuery, !> {
|
||||||
let mut res: PlumeQuery = Default::default();
|
let mut res: PlumeQuery = Default::default();
|
||||||
|
|
||||||
res.from_str_req(query.trim());
|
res.from_str_req(&query.trim());
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
use crate::CONFIG;
|
|
||||||
use rocket::request::{FromRequest, Outcome, Request};
|
|
||||||
use std::fmt;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
pub enum Strategy {
|
|
||||||
Password,
|
|
||||||
Email,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Strategy {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Password
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Strategy {
|
|
||||||
type Err = StrategyError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
use self::Strategy::*;
|
|
||||||
|
|
||||||
match s {
|
|
||||||
"password" => Ok(Password),
|
|
||||||
"email" => Ok(Email),
|
|
||||||
s => Err(StrategyError::Unsupported(s.to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum StrategyError {
|
|
||||||
Unsupported(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for StrategyError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
use self::StrategyError::*;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
// FIXME: Calc option strings from enum
|
|
||||||
Unsupported(s) => write!(f, "Unsupported strategy: {}. Choose password or email", s),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for StrategyError {}
|
|
||||||
|
|
||||||
pub struct Password();
|
|
||||||
pub struct Email();
|
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for Password {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn from_request(_request: &'a Request<'r>) -> Outcome<Self, ()> {
|
|
||||||
match matches!(CONFIG.signup, Strategy::Password) {
|
|
||||||
true => Outcome::Success(Self()),
|
|
||||||
false => Outcome::Forward(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for Email {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn from_request(_request: &'a Request<'r>) -> Outcome<Self, ()> {
|
|
||||||
match matches!(CONFIG.signup, Strategy::Email) {
|
|
||||||
true => Outcome::Success(Self()),
|
|
||||||
false => Outcome::Forward(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,12 @@ pub enum QueryError {
|
|||||||
RuntimeError(String),
|
RuntimeError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::option::NoneError> for QueryError {
|
||||||
|
fn from(_: std::option::NoneError) -> Self {
|
||||||
|
QueryError::UnexpectedEndOfQuery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type QueryResult<T> = std::result::Result<T, QueryError>;
|
pub type QueryResult<T> = std::result::Result<T, QueryError>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
@@ -233,7 +239,7 @@ impl WithList {
|
|||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
match list {
|
match list {
|
||||||
List::List(name) => {
|
List::List(name) => {
|
||||||
let list = lists::List::find_for_user_by_name(conn, timeline.user_id, name)?;
|
let list = lists::List::find_for_user_by_name(conn, timeline.user_id, &name)?;
|
||||||
match (self, list.kind()) {
|
match (self, list.kind()) {
|
||||||
(WithList::Blog, ListType::Blog) => list.contains_blog(conn, post.blog_id),
|
(WithList::Blog, ListType::Blog) => list.contains_blog(conn, post.blog_id),
|
||||||
(WithList::Author { boosts, likes }, ListType::User) => match kind {
|
(WithList::Author { boosts, likes }, ListType::User) => match kind {
|
||||||
@@ -408,7 +414,7 @@ enum List<'a> {
|
|||||||
|
|
||||||
fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
|
fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
let (left, token) = parse_a(stream)?;
|
let (left, token) = parse_a(&stream)?;
|
||||||
res.push(token);
|
res.push(token);
|
||||||
stream = left;
|
stream = left;
|
||||||
while !stream.is_empty() {
|
while !stream.is_empty() {
|
||||||
@@ -430,7 +436,7 @@ fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
|
|
||||||
fn parse_a<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
|
fn parse_a<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
let (left, token) = parse_b(stream)?;
|
let (left, token) = parse_b(&stream)?;
|
||||||
res.push(token);
|
res.push(token);
|
||||||
stream = left;
|
stream = left;
|
||||||
while !stream.is_empty() {
|
while !stream.is_empty() {
|
||||||
@@ -457,7 +463,7 @@ fn parse_b<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<
|
|||||||
match left.get(0) {
|
match left.get(0) {
|
||||||
Some(Token::RParent(_)) => Ok((&left[1..], token)),
|
Some(Token::RParent(_)) => Ok((&left[1..], token)),
|
||||||
Some(t) => t.get_error(Token::RParent(0)),
|
Some(t) => t.get_error(Token::RParent(0)),
|
||||||
None => Err(QueryError::UnexpectedEndOfQuery),
|
None => None?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => parse_c(stream),
|
_ => parse_c(stream),
|
||||||
@@ -478,13 +484,9 @@ fn parse_c<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Arg<'a>)> {
|
fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Arg<'a>)> {
|
||||||
match stream
|
match stream.get(0).map(Token::get_text)? {
|
||||||
.get(0)
|
|
||||||
.map(Token::get_text)
|
|
||||||
.ok_or(QueryError::UnexpectedEndOfQuery)?
|
|
||||||
{
|
|
||||||
s @ "blog" | s @ "author" | s @ "license" | s @ "tags" | s @ "lang" => {
|
s @ "blog" | s @ "author" | s @ "license" | s @ "tags" | s @ "lang" => {
|
||||||
match stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
match stream.get(1)? {
|
||||||
Token::Word(_, _, r#in) if r#in == &"in" => {
|
Token::Word(_, _, r#in) if r#in == &"in" => {
|
||||||
let (mut left, list) = parse_l(&stream[2..])?;
|
let (mut left, list) = parse_l(&stream[2..])?;
|
||||||
let kind = match s {
|
let kind = match s {
|
||||||
@@ -496,12 +498,7 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
if *clude != "include" && *clude != "exclude" {
|
if *clude != "include" && *clude != "exclude" {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match (
|
match (*clude, left.get(1).map(Token::get_text)?) {
|
||||||
*clude,
|
|
||||||
left.get(1)
|
|
||||||
.map(Token::get_text)
|
|
||||||
.ok_or(QueryError::UnexpectedEndOfQuery)?,
|
|
||||||
) {
|
|
||||||
("include", "reshares") | ("include", "reshare") => {
|
("include", "reshares") | ("include", "reshare") => {
|
||||||
boosts = true
|
boosts = true
|
||||||
}
|
}
|
||||||
@@ -532,10 +529,7 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
t => t.get_error(Token::Word(0, 0, "'in'")),
|
t => t.get_error(Token::Word(0, 0, "'in'")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s @ "title" | s @ "subtitle" | s @ "content" => match (
|
s @ "title" | s @ "subtitle" | s @ "content" => match (stream.get(1)?, stream.get(2)?) {
|
||||||
stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)?,
|
|
||||||
stream.get(2).ok_or(QueryError::UnexpectedEndOfQuery)?,
|
|
||||||
) {
|
|
||||||
(Token::Word(_, _, contains), Token::Word(_, _, w)) if contains == &"contains" => Ok((
|
(Token::Word(_, _, contains), Token::Word(_, _, w)) if contains == &"contains" => Ok((
|
||||||
&stream[3..],
|
&stream[3..],
|
||||||
Arg::Contains(
|
Arg::Contains(
|
||||||
@@ -561,13 +555,7 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
if *clude != "include" && *clude != "exclude" {
|
if *clude != "include" && *clude != "exclude" {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match (
|
match (*clude, stream.get(2).map(Token::get_text)?) {
|
||||||
*clude,
|
|
||||||
stream
|
|
||||||
.get(2)
|
|
||||||
.map(Token::get_text)
|
|
||||||
.ok_or(QueryError::UnexpectedEndOfQuery)?,
|
|
||||||
) {
|
|
||||||
("include", "reshares") | ("include", "reshare") => boosts = true,
|
("include", "reshares") | ("include", "reshare") => boosts = true,
|
||||||
("exclude", "reshares") | ("exclude", "reshare") => boosts = false,
|
("exclude", "reshares") | ("exclude", "reshare") => boosts = false,
|
||||||
("include", "likes") | ("include", "like") => likes = true,
|
("include", "likes") | ("include", "like") => likes = true,
|
||||||
@@ -589,23 +577,20 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
"all" => Ok((&stream[1..], Arg::Boolean(Bool::All))),
|
"all" => Ok((&stream[1..], Arg::Boolean(Bool::All))),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
_ => stream
|
_ => stream.get(0)?.get_error(Token::Word(
|
||||||
.get(0)
|
0,
|
||||||
.ok_or(QueryError::UnexpectedEndOfQuery)?
|
0,
|
||||||
.get_error(Token::Word(
|
"one of 'blog', 'author', 'license', 'tags', 'lang', \
|
||||||
0,
|
|
||||||
0,
|
|
||||||
"one of 'blog', 'author', 'license', 'tags', 'lang', \
|
|
||||||
'title', 'subtitle', 'content', 'followed', 'has_cover', 'local' or 'all'",
|
'title', 'subtitle', 'content', 'followed', 'has_cover', 'local' or 'all'",
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], List<'a>)> {
|
fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], List<'a>)> {
|
||||||
match stream.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
match stream.get(0)? {
|
||||||
Token::LBracket(_) => {
|
Token::LBracket(_) => {
|
||||||
let (left, list) = parse_m(&stream[1..])?;
|
let (left, list) = parse_m(&stream[1..])?;
|
||||||
match left.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
match left.get(0)? {
|
||||||
Token::RBracket(_) => Ok((&left[1..], List::Array(list))),
|
Token::RBracket(_) => Ok((&left[1..], List::Array(list))),
|
||||||
t => t.get_error(Token::Word(0, 0, "one of ']' or ','")),
|
t => t.get_error(Token::Word(0, 0, "one of ']' or ','")),
|
||||||
}
|
}
|
||||||
@@ -616,20 +601,16 @@ fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Lis
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_m<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Vec<&'a str>)> {
|
fn parse_m<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Vec<&'a str>)> {
|
||||||
let mut res: Vec<&str> = vec![
|
let mut res: Vec<&str> = vec![match stream.get(0)? {
|
||||||
match stream.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
Token::Word(_, _, w) => w,
|
||||||
Token::Word(_, _, w) => w,
|
t => return t.get_error(Token::Word(0, 0, "any word")),
|
||||||
t => return t.get_error(Token::Word(0, 0, "any word")),
|
}];
|
||||||
},
|
|
||||||
];
|
|
||||||
stream = &stream[1..];
|
stream = &stream[1..];
|
||||||
while let Token::Comma(_) = stream[0] {
|
while let Token::Comma(_) = stream[0] {
|
||||||
res.push(
|
res.push(match stream.get(1)? {
|
||||||
match stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
Token::Word(_, _, w) => w,
|
||||||
Token::Word(_, _, w) => w,
|
t => return t.get_error(Token::Word(0, 0, "any word")),
|
||||||
t => return t.get_error(Token::Word(0, 0, "any word")),
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
stream = &stream[2..];
|
stream = &stream[2..];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+80
-51
@@ -22,13 +22,17 @@ use openssl::{
|
|||||||
};
|
};
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
|
ap_accept_header,
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
request::get,
|
sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer},
|
||||||
sign::{gen_keypair, Error as SignError, Result as SignResult, Signer},
|
|
||||||
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
|
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
use reqwest::{
|
||||||
|
header::{HeaderValue, ACCEPT},
|
||||||
|
ClientBuilder,
|
||||||
|
};
|
||||||
use riker::actors::{Publish, Tell};
|
use riker::actors::{Publish, Tell};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
outcome::IntoOutcome,
|
outcome::IntoOutcome,
|
||||||
@@ -48,7 +52,6 @@ pub enum Role {
|
|||||||
Admin = 0,
|
Admin = 0,
|
||||||
Moderator = 1,
|
Moderator = 1,
|
||||||
Normal = 2,
|
Normal = 2,
|
||||||
Instance = 3,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)]
|
#[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)]
|
||||||
@@ -75,7 +78,6 @@ pub struct User {
|
|||||||
pub summary_html: SafeString,
|
pub summary_html: SafeString,
|
||||||
/// 0 = admin
|
/// 0 = admin
|
||||||
/// 1 = moderator
|
/// 1 = moderator
|
||||||
/// 3 = local instance
|
|
||||||
/// anything else = normal user
|
/// anything else = normal user
|
||||||
pub role: i32,
|
pub role: i32,
|
||||||
pub preferred_theme: Option<String>,
|
pub preferred_theme: Option<String>,
|
||||||
@@ -202,22 +204,6 @@ impl User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: Should create user record with normalized(lowercased) email
|
|
||||||
*/
|
|
||||||
pub fn email_used(conn: &DbConn, email: &str) -> Result<bool> {
|
|
||||||
use diesel::dsl::{exists, select};
|
|
||||||
|
|
||||||
select(exists(
|
|
||||||
users::table
|
|
||||||
.filter(users::instance_id.eq(Instance::get_local()?.id))
|
|
||||||
.filter(users::email.eq(email))
|
|
||||||
.or_filter(users::email.eq(email.to_ascii_lowercase())),
|
|
||||||
))
|
|
||||||
.get_result(&**conn)
|
|
||||||
.map_err(Error::from)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<User> {
|
fn fetch_from_webfinger(conn: &DbConn, acct: &str) -> Result<User> {
|
||||||
let link = resolve(acct.to_owned(), true)?
|
let link = resolve(acct.to_owned(), true)?
|
||||||
.links
|
.links
|
||||||
@@ -226,7 +212,8 @@ impl User {
|
|||||||
.ok_or(Error::Webfinger)?;
|
.ok_or(Error::Webfinger)?;
|
||||||
User::from_id(
|
User::from_id(
|
||||||
conn,
|
conn,
|
||||||
link.href.as_ref().ok_or(Error::Webfinger)?,
|
&Instance::get_local().expect("Failed to get local instance"),
|
||||||
|
link.href.as_ref()?,
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
)
|
)
|
||||||
@@ -243,7 +230,20 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(url: &str) -> Result<CustomPerson> {
|
fn fetch(url: &str) -> Result<CustomPerson> {
|
||||||
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
let mut res = ClientBuilder::new()
|
||||||
|
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
||||||
|
.build()?
|
||||||
|
.get(url)
|
||||||
|
.header(
|
||||||
|
ACCEPT,
|
||||||
|
HeaderValue::from_str(
|
||||||
|
&ap_accept_header()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
)?,
|
||||||
|
)
|
||||||
|
.send()?;
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
// without this workaround, publicKey is not correctly deserialized
|
// without this workaround, publicKey is not correctly deserialized
|
||||||
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
||||||
@@ -262,10 +262,10 @@ impl User {
|
|||||||
conn,
|
conn,
|
||||||
json.object
|
json.object
|
||||||
.object_props
|
.object_props
|
||||||
.icon_image()? // FIXME: Fails when icon is not set
|
.icon_image()?
|
||||||
.object_props
|
.object_props
|
||||||
.url_string()?,
|
.url_string()?,
|
||||||
self,
|
&self,
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@@ -434,12 +434,12 @@ impl User {
|
|||||||
let last = &format!(
|
let last = &format!(
|
||||||
"{}?page={}",
|
"{}?page={}",
|
||||||
&self.outbox_url,
|
&self.outbox_url,
|
||||||
self.get_activities_count(conn) / i64::from(ITEMS_PER_PAGE) + 1
|
self.get_activities_count(&conn) / i64::from(ITEMS_PER_PAGE) + 1
|
||||||
);
|
);
|
||||||
coll.collection_props.set_first_link(Id::new(first))?;
|
coll.collection_props.set_first_link(Id::new(first))?;
|
||||||
coll.collection_props.set_last_link(Id::new(last))?;
|
coll.collection_props.set_last_link(Id::new(last))?;
|
||||||
coll.collection_props
|
coll.collection_props
|
||||||
.set_total_items_u64(self.get_activities_count(conn) as u64)?;
|
.set_total_items_u64(self.get_activities_count(&conn) as u64)?;
|
||||||
Ok(ActivityStream::new(coll))
|
Ok(ActivityStream::new(coll))
|
||||||
}
|
}
|
||||||
pub fn outbox_page(
|
pub fn outbox_page(
|
||||||
@@ -448,7 +448,7 @@ impl User {
|
|||||||
(min, max): (i32, i32),
|
(min, max): (i32, i32),
|
||||||
) -> Result<ActivityStream<OrderedCollectionPage>> {
|
) -> Result<ActivityStream<OrderedCollectionPage>> {
|
||||||
let acts = self.get_activities_page(conn, (min, max))?;
|
let acts = self.get_activities_page(conn, (min, max))?;
|
||||||
let n_acts = self.get_activities_count(conn);
|
let n_acts = self.get_activities_count(&conn);
|
||||||
let mut coll = OrderedCollectionPage::default();
|
let mut coll = OrderedCollectionPage::default();
|
||||||
if n_acts - i64::from(min) >= i64::from(ITEMS_PER_PAGE) {
|
if n_acts - i64::from(min) >= i64::from(ITEMS_PER_PAGE) {
|
||||||
coll.collection_page_props.set_next_link(Id::new(&format!(
|
coll.collection_page_props.set_next_link(Id::new(&format!(
|
||||||
@@ -470,7 +470,20 @@ impl User {
|
|||||||
Ok(ActivityStream::new(coll))
|
Ok(ActivityStream::new(coll))
|
||||||
}
|
}
|
||||||
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
|
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
|
||||||
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
let mut res = ClientBuilder::new()
|
||||||
|
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
||||||
|
.build()?
|
||||||
|
.get(url)
|
||||||
|
.header(
|
||||||
|
ACCEPT,
|
||||||
|
HeaderValue::from_str(
|
||||||
|
&ap_accept_header()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
)?,
|
||||||
|
)
|
||||||
|
.send()?;
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
let items = json["items"]
|
let items = json["items"]
|
||||||
@@ -484,11 +497,20 @@ impl User {
|
|||||||
Ok((items, next))
|
Ok((items, next))
|
||||||
}
|
}
|
||||||
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
||||||
let mut res = get(
|
let mut res = ClientBuilder::new()
|
||||||
&self.outbox_url[..],
|
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
||||||
Self::get_sender(),
|
.build()?
|
||||||
CONFIG.proxy().cloned(),
|
.get(&self.outbox_url[..])
|
||||||
)?;
|
.header(
|
||||||
|
ACCEPT,
|
||||||
|
HeaderValue::from_str(
|
||||||
|
&ap_accept_header()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
)?,
|
||||||
|
)
|
||||||
|
.send()?;
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
if let Some(first) = json.get("first") {
|
if let Some(first) = json.get("first") {
|
||||||
@@ -498,7 +520,7 @@ impl User {
|
|||||||
if page.is_empty() {
|
if page.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
items.append(&mut page);
|
items.extend(page.drain(..));
|
||||||
if let Some(n) = nxt {
|
if let Some(n) = nxt {
|
||||||
if n == next {
|
if n == next {
|
||||||
break;
|
break;
|
||||||
@@ -520,11 +542,20 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
||||||
let mut res = get(
|
let mut res = ClientBuilder::new()
|
||||||
&self.followers_endpoint[..],
|
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
||||||
Self::get_sender(),
|
.build()?
|
||||||
CONFIG.proxy().cloned(),
|
.get(&self.followers_endpoint[..])
|
||||||
)?;
|
.header(
|
||||||
|
ACCEPT,
|
||||||
|
HeaderValue::from_str(
|
||||||
|
&ap_accept_header()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
)?,
|
||||||
|
)
|
||||||
|
.send()?;
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
Ok(json["items"]
|
Ok(json["items"]
|
||||||
@@ -696,7 +727,7 @@ impl User {
|
|||||||
|
|
||||||
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
||||||
PKey::from_rsa(Rsa::private_key_from_pem(
|
PKey::from_rsa(Rsa::private_key_from_pem(
|
||||||
self.private_key.clone().ok_or(Error::Signature)?.as_ref(),
|
self.private_key.clone()?.as_ref(),
|
||||||
)?)
|
)?)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
@@ -919,7 +950,7 @@ impl FromId<DbConn> for User {
|
|||||||
|
|
||||||
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> {
|
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> {
|
||||||
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
||||||
let inst = url.host_str().ok_or(Error::Url)?;
|
let inst = url.host_str()?;
|
||||||
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
|
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
|
||||||
Instance::insert(
|
Instance::insert(
|
||||||
conn,
|
conn,
|
||||||
@@ -934,6 +965,8 @@ impl FromId<DbConn> for User {
|
|||||||
open_registrations: true,
|
open_registrations: true,
|
||||||
short_description_html: String::new(),
|
short_description_html: String::new(),
|
||||||
long_description_html: String::new(),
|
long_description_html: String::new(),
|
||||||
|
private_key: None,
|
||||||
|
public_key: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
@@ -1007,10 +1040,6 @@ impl FromId<DbConn> for User {
|
|||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sender() -> &'static dyn Signer {
|
|
||||||
Instance::get_local_instance_user().expect("Failed to local instance user")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&DbConn> for User {
|
impl AsActor<&DbConn> for User {
|
||||||
@@ -1047,18 +1076,18 @@ impl Signer for User {
|
|||||||
format!("{}#main-key", self.ap_url)
|
format!("{}#main-key", self.ap_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
|
||||||
let key = self.get_keypair().map_err(|_| SignError())?;
|
let key = self.get_keypair()?;
|
||||||
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
|
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
|
||||||
signer.update(to_sign.as_bytes())?;
|
signer.update(to_sign.as_bytes())?;
|
||||||
signer.sign_to_vec().map_err(SignError::from)
|
signer.sign_to_vec().map_err(SignatureError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
||||||
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
|
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
|
||||||
verifier.update(data.as_bytes())?;
|
verifier.update(data.as_bytes())?;
|
||||||
verifier.verify(signature).map_err(SignError::from)
|
verifier.verify(&signature).map_err(SignatureError::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1099,7 +1128,7 @@ impl NewUser {
|
|||||||
display_name,
|
display_name,
|
||||||
role: role as i32,
|
role: role as i32,
|
||||||
summary: summary.to_owned(),
|
summary: summary.to_owned(),
|
||||||
summary_html: SafeString::new(&utils::md_to_html(summary, None, false, None).0),
|
summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0),
|
||||||
email: Some(email),
|
email: Some(email),
|
||||||
hashed_password: password,
|
hashed_password: password,
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Afrikaans\n"
|
"Language-Team: Afrikaans\n"
|
||||||
"Language: af_ZA\n"
|
"Language: af_ZA\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Arabic\n"
|
"Language-Team: Arabic\n"
|
||||||
"Language: ar_SA\n"
|
"Language: ar_SA\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "فتح محرر النصوص الغني"
|
msgstr "فتح محرر النصوص الغني"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "العنوان"
|
msgstr "العنوان"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "العنوان الثانوي أو الملخص"
|
msgstr "العنوان الثانوي أو الملخص"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "اكتب مقالك هنا. ماركداون مُدَعَّم."
|
msgstr "اكتب مقالك هنا. ماركداون مُدَعَّم."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "يتبقا {} حرفا تقريبا"
|
msgstr "يتبقا {} حرفا تقريبا"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "الوسوم"
|
msgstr "الوسوم"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "الرخصة"
|
msgstr "الرخصة"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "الغلاف"
|
msgstr "الغلاف"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "هذه مسودة"
|
msgstr "هذه مسودة"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "نشر كتابا"
|
msgstr "نشر كتابا"
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Bulgarian\n"
|
"Language-Team: Bulgarian\n"
|
||||||
"Language: bg_BG\n"
|
"Language: bg_BG\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "Искате ли да активирате локално автоматично запаметяване, последно редактирано в {}?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Отворете редактора с богат текст"
|
msgstr "Отворете редактора с богат текст"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Заглавие"
|
msgstr "Заглавие"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Подзаглавие или резюме"
|
msgstr "Подзаглавие или резюме"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Напишете статията си тук. Поддържа се Markdown."
|
msgstr "Напишете статията си тук. Поддържа се Markdown."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Остават {} знака вляво"
|
msgstr "Остават {} знака вляво"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Етикети"
|
msgstr "Етикети"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Лиценз"
|
msgstr "Лиценз"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Основно изображение"
|
msgstr "Основно изображение"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Това е проект"
|
msgstr "Това е проект"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Публикувай"
|
msgstr "Публикувай"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Catalan\n"
|
"Language-Team: Catalan\n"
|
||||||
"Language: ca_ES\n"
|
"Language: ca_ES\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Obre l’editor de text enriquit"
|
msgstr "Obre l’editor de text enriquit"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Títol"
|
msgstr "Títol"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Subtítol o resum"
|
msgstr "Subtítol o resum"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Escriviu el vostre article ací. Podeu fer servir el Markdown."
|
msgstr "Escriviu el vostre article ací. Podeu fer servir el Markdown."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Queden uns {} caràcters"
|
msgstr "Queden uns {} caràcters"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiquetes"
|
msgstr "Etiquetes"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Llicència"
|
msgstr "Llicència"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Coberta"
|
msgstr "Coberta"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Açò és un esborrany"
|
msgstr "Açò és un esborrany"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publica"
|
msgstr "Publica"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Czech\n"
|
"Language-Team: Czech\n"
|
||||||
"Language: cs_CZ\n"
|
"Language: cs_CZ\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Otevřít editor formátovaného textu"
|
msgstr "Otevřít editor formátovaného textu"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Nadpis"
|
msgstr "Nadpis"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Podnadpis, nebo shrnutí"
|
msgstr "Podnadpis, nebo shrnutí"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Sem napište svůj článek. Markdown je podporován."
|
msgstr "Sem napište svůj článek. Markdown je podporován."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Zbývá kolem {} znaků"
|
msgstr "Zbývá kolem {} znaků"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Tagy"
|
msgstr "Tagy"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licence"
|
msgstr "Licence"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Titulka"
|
msgstr "Titulka"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Tohle je koncept"
|
msgstr "Tohle je koncept"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Zveřejnit"
|
msgstr "Zveřejnit"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Danish\n"
|
"Language-Team: Danish\n"
|
||||||
"Language: da_DK\n"
|
"Language: da_DK\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
"Language: de_DE\n"
|
"Language: de_DE\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "Möchten Sie die lokale automatische Speicherung laden, die zuletzt um {} bearbeitet wurde?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr " Rich Text Editor (RTE) öffnen"
|
msgstr " Rich Text Editor (RTE) öffnen"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titel"
|
msgstr "Titel"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Untertitel oder Zusammenfassung"
|
msgstr "Untertitel oder Zusammenfassung"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Schreiben deinen Artikel hier. Markdown wird unterstützt."
|
msgstr "Schreiben deinen Artikel hier. Markdown wird unterstützt."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Ungefähr {} Zeichen übrig"
|
msgstr "Ungefähr {} Zeichen übrig"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Schlagwörter"
|
msgstr "Schlagwörter"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Lizenz"
|
msgstr "Lizenz"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Einband"
|
msgstr "Einband"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Dies ist ein Entwurf"
|
msgstr "Dies ist ein Entwurf"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Veröffentlichen"
|
msgstr "Veröffentlichen"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Greek\n"
|
"Language-Team: Greek\n"
|
||||||
"Language: el_GR\n"
|
"Language: el_GR\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: English\n"
|
"Language-Team: English\n"
|
||||||
"Language: en_US\n"
|
"Language: en_US\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Esperanto\n"
|
"Language-Team: Esperanto\n"
|
||||||
"Language: eo_UY\n"
|
"Language: eo_UY\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Malfermi la riĉan redaktilon"
|
msgstr "Malfermi la riĉan redaktilon"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titolo"
|
msgstr "Titolo"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Verku vian artikolon ĉi tie. Markdown estas subtenita."
|
msgstr "Verku vian artikolon ĉi tie. Markdown estas subtenita."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Proksimume {} signoj restantaj"
|
msgstr "Proksimume {} signoj restantaj"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etikedoj"
|
msgstr "Etikedoj"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Permesilo"
|
msgstr "Permesilo"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Kovro"
|
msgstr "Kovro"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Malfinias"
|
msgstr "Malfinias"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Eldoni"
|
msgstr "Eldoni"
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Spanish\n"
|
"Language-Team: Spanish\n"
|
||||||
"Language: es_ES\n"
|
"Language: es_ES\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "¿Quieres cargar el guardado automático local editado por última vez en {}?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Abrir el editor de texto enriquecido"
|
msgstr "Abrir el editor de texto enriquecido"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Título"
|
msgstr "Título"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Subtítulo, o resumen"
|
msgstr "Subtítulo, o resumen"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Escriba su artículo aquí. Puede utilizar Markdown."
|
msgstr "Escriba su artículo aquí. Puede utilizar Markdown."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Quedan unos {} caracteres"
|
msgstr "Quedan unos {} caracteres"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiquetas"
|
msgstr "Etiquetas"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licencia"
|
msgstr "Licencia"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Cubierta"
|
msgstr "Cubierta"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Esto es un borrador"
|
msgstr "Esto es un borrador"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publicar"
|
msgstr "Publicar"
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Persian\n"
|
"Language-Team: Persian\n"
|
||||||
"Language: fa_IR\n"
|
"Language: fa_IR\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "آیا میخواهید نسخهٔ ذخیره شدهٔ خودکار محلّی از آخرین ویرایش در {} را بار کنید؟"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "باز کردن ویرایشگر غنی"
|
msgstr "باز کردن ویرایشگر غنی"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "عنوان"
|
msgstr "عنوان"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "زیرعنوان، یا چکیده"
|
msgstr "زیرعنوان، یا چکیده"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "مقالهتان را اینجا بنویسید. از مارکداون پشتیبانی میشود."
|
msgstr "مقاله را اینجا بنویسید. از مارکداون پشتیبانی میشود."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "نزدیک به {} حرف باقی مانده است"
|
msgstr "نزدیک به {} حرف باقی مانده است"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "برچسبها"
|
msgstr "برچسبها"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "پروانه"
|
msgstr "پروانه"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "جلد"
|
msgstr "تصویر شاخص"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "این، یک پیشنویس است"
|
msgstr "این، یک پیشنویس است"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "انتشار"
|
msgstr "انتشار"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Finnish\n"
|
"Language-Team: Finnish\n"
|
||||||
"Language: fi_FI\n"
|
"Language: fi_FI\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Avaa edistynyt tekstieditori"
|
msgstr "Avaa edistynyt tekstieditori"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Otsikko"
|
msgstr "Otsikko"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Alaotsikko tai tiivistelmä"
|
msgstr "Alaotsikko tai tiivistelmä"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Kirjoita artikkelisi tähän. Markdown -kuvauskieli on tuettu."
|
msgstr "Kirjoita artikkelisi tähän. Markdown -kuvauskieli on tuettu."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "%{count} merkkiä jäljellä"
|
msgstr "%{count} merkkiä jäljellä"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Tagit"
|
msgstr "Tagit"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Lisenssi"
|
msgstr "Lisenssi"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Kansi"
|
msgstr "Kansi"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Tämä on luonnos"
|
msgstr "Tämä on luonnos"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Julkaise"
|
msgstr "Julkaise"
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: French\n"
|
"Language-Team: French\n"
|
||||||
"Language: fr_FR\n"
|
"Language: fr_FR\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "Voulez vous charger la sauvegarde automatique locale, éditée la dernière fois à {}?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Ouvrir l'éditeur de texte avancé"
|
msgstr "Ouvrir l'éditeur de texte avancé"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titre"
|
msgstr "Titre"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Sous-titre ou résumé"
|
msgstr "Sous-titre ou résumé"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Écrivez votre article ici. Vous pouvez utiliser du Markdown."
|
msgstr "Écrivez votre article ici. Vous pouvez utiliser du Markdown."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Environ {} caractères restant"
|
msgstr "Environ {} caractères restant"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Étiquettes"
|
msgstr "Étiquettes"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licence"
|
msgstr "Licence"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Illustration"
|
msgstr "Illustration"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Ceci est un brouillon"
|
msgstr "Ceci est un brouillon"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publier"
|
msgstr "Publier"
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Galician\n"
|
"Language-Team: Galician\n"
|
||||||
"Language: gl_ES\n"
|
"Language: gl_ES\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "Queres cargar a última copia gardada editada o {}?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Abre o editor de texto enriquecido"
|
msgstr "Abre o editor de texto enriquecido"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Título"
|
msgstr "Título"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Subtítulo, ou resumo"
|
msgstr "Subtítulo, ou resumo"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Escribe aquí o teu artigo: podes utilizar Markdown."
|
msgstr "Escribe aquí o teu artigo: podes utilizar Markdown."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Dispós de {} caracteres"
|
msgstr "Dispós de {} caracteres"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiquetas"
|
msgstr "Etiquetas"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licenza"
|
msgstr "Licenza"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Portada"
|
msgstr "Portada"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Este é un borrador"
|
msgstr "Este é un borrador"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publicar"
|
msgstr "Publicar"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hebrew\n"
|
"Language-Team: Hebrew\n"
|
||||||
"Language: he_IL\n"
|
"Language: he_IL\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hindi\n"
|
"Language-Team: Hindi\n"
|
||||||
"Language: hi_IN\n"
|
"Language: hi_IN\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "शीर्षक"
|
msgstr "शीर्षक"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "अपना आर्टिकल या लेख यहाँ लिखें. Markdown उपलब्ध है."
|
msgstr "अपना आर्टिकल या लेख यहाँ लिखें. Markdown उपलब्ध है."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "लगभग {} अक्षर बाकी हैं"
|
msgstr "लगभग {} अक्षर बाकी हैं"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "टैग्स"
|
msgstr "टैग्स"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "लाइसेंस"
|
msgstr "लाइसेंस"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "पब्लिश करें"
|
msgstr "पब्लिश करें"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Croatian\n"
|
"Language-Team: Croatian\n"
|
||||||
"Language: hr_HR\n"
|
"Language: hr_HR\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Naslov"
|
msgstr "Naslov"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Tagovi"
|
msgstr "Tagovi"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licenca"
|
msgstr "Licenca"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Objavi"
|
msgstr "Objavi"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Hungarian\n"
|
"Language-Team: Hungarian\n"
|
||||||
"Language: hu_HU\n"
|
"Language: hu_HU\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Italian\n"
|
"Language-Team: Italian\n"
|
||||||
"Language: it_IT\n"
|
"Language: it_IT\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Apri il compositore di testo avanzato"
|
msgstr "Apri il compositore di testo avanzato"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titolo"
|
msgstr "Titolo"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Sottotitolo, o sommario"
|
msgstr "Sottotitolo, o sommario"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Scrivi qui il tuo articolo. È supportato il Markdown."
|
msgstr "Scrivi qui il tuo articolo. È supportato il Markdown."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Circa {} caratteri rimasti"
|
msgstr "Circa {} caratteri rimasti"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etichette"
|
msgstr "Etichette"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licenza"
|
msgstr "Licenza"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Copertina"
|
msgstr "Copertina"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Questa è una bozza"
|
msgstr "Questa è una bozza"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Pubblica"
|
msgstr "Pubblica"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Japanese\n"
|
"Language-Team: Japanese\n"
|
||||||
"Language: ja_JP\n"
|
"Language: ja_JP\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "リッチテキストエディターを開く"
|
msgstr "リッチテキストエディターを開く"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "タイトル"
|
msgstr "タイトル"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "サブタイトル、または概要"
|
msgstr "サブタイトル、または概要"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "投稿をここに書きます。Markdown がサポートされています。"
|
msgstr "投稿をここに書きます。Markdown がサポートされています。"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "残り約 {} 文字"
|
msgstr "残り約 {} 文字"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "タグ"
|
msgstr "タグ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "ライセンス"
|
msgstr "ライセンス"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "カバー"
|
msgstr "カバー"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "これは下書きです"
|
msgstr "これは下書きです"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "公開"
|
msgstr "公開"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Korean\n"
|
"Language-Team: Korean\n"
|
||||||
"Language: ko_KR\n"
|
"Language: ko_KR\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Dutch\n"
|
"Language-Team: Dutch\n"
|
||||||
"Language: nl_NL\n"
|
"Language: nl_NL\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "Wilt u de lokale auto-opslaan laden? Laatst bewerkt om: {}"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Open de rich-text editor"
|
msgstr "Open de rich-text editor"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titel"
|
msgstr "Titel"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Ondertitel of samenvatting"
|
msgstr "Ondertitel of samenvatting"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Schrijf hier je artikel. Markdown wordt ondersteund."
|
msgstr "Schrijf hier je artikel. Markdown wordt ondersteund."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Ongeveer {} tekens over"
|
msgstr "Ongeveer {} tekens over"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Tags"
|
msgstr "Tags"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licentie"
|
msgstr "Licentie"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Hoofdafbeelding"
|
msgstr "Hoofdafbeelding"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Dit is een concept"
|
msgstr "Dit is een concept"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publiceren"
|
msgstr "Publiceren"
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Norwegian\n"
|
"Language-Team: Norwegian\n"
|
||||||
"Language: no_NO\n"
|
"Language: no_NO\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Lisens"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publiser"
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Polish\n"
|
"Language-Team: Polish\n"
|
||||||
"Language: pl_PL\n"
|
"Language: pl_PL\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Otwórz edytor tekstu sformatowanego"
|
msgstr "Otwórz edytor tekstu sformatowanego"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Tytuł"
|
msgstr "Tytuł"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Podtytuł, lub podsumowanie"
|
msgstr "Podtytuł, lub podsumowanie"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Napisz swój artykuł tutaj. Markdown jest obsługiwany."
|
msgstr "Napisz swój artykuł tutaj. Markdown jest obsługiwany."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Pozostało w okolicy {} znaków"
|
msgstr "Pozostało w okolicy {} znaków"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Tagi"
|
msgstr "Tagi"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licencja"
|
msgstr "Licencja"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Okładka"
|
msgstr "Okładka"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "To jest szkic"
|
msgstr "To jest szkic"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publikuj"
|
msgstr "Publikuj"
|
||||||
|
|
||||||
|
|||||||
@@ -12,46 +12,46 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:188
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:281
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:314
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:318
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:325
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:336
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:413
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:414
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:417
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:437
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:444
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
+13
-13
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Portuguese, Brazilian\n"
|
"Language-Team: Portuguese, Brazilian\n"
|
||||||
"Language: pt_BR\n"
|
"Language: pt_BR\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "Você quer carregar o último conteúdo salvo localmente editado em {}?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Abrir o editor de rich text"
|
msgstr "Abrir o editor de rich text"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Título"
|
msgstr "Título"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Subtítulo ou resumo"
|
msgstr "Subtítulo ou resumo"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Escreva seu artigo aqui. Markdown é suportado."
|
msgstr "Escreva seu artigo aqui. Markdown é suportado."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Cerca de {} caracteres restantes"
|
msgstr "Cerca de {} caracteres restantes"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Tags"
|
msgstr "Tags"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licença"
|
msgstr "Licença"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Capa"
|
msgstr "Capa"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Isso é um rascunho"
|
msgstr "Isso é um rascunho"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publicar"
|
msgstr "Publicar"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Romanian\n"
|
"Language-Team: Romanian\n"
|
||||||
"Language: ro_RO\n"
|
"Language: ro_RO\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Deschide editorul de text"
|
msgstr "Deschide editorul de text"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titlu"
|
msgstr "Titlu"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Scrie articolul tău aici. Markdown este acceptat."
|
msgstr "Scrie articolul tău aici. Markdown este acceptat."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "În apropiere de {} caractere rămase"
|
msgstr "În apropiere de {} caractere rămase"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etichete"
|
msgstr "Etichete"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licenţă"
|
msgstr "Licenţă"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Coperta"
|
msgstr "Coperta"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Aceasta este o ciornă"
|
msgstr "Aceasta este o ciornă"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publică"
|
msgstr "Publică"
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Language: ru_RU\n"
|
"Language: ru_RU\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "Хотите ли вы загрузить локальное автосохранение, сделанное в {}?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Открыть в визуальном редакторе"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Заголовок"
|
msgstr "Заголовок"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Подзаголовок или резюме"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Пишите свою статью здесь. Markdown поддерживается."
|
msgstr "Пишите свою статью здесь. Markdown поддерживается."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Осталось около {} символов"
|
msgstr "Осталось около {} символов"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Теги"
|
msgstr "Теги"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Лицензия"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Обложка"
|
msgstr "Обложка"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Это черновик"
|
msgstr "Это черновик"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Опубликовать"
|
msgstr "Опубликовать"
|
||||||
|
|
||||||
|
|||||||
+18
-18
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Santali\n"
|
"Language-Team: Santali\n"
|
||||||
"Language: sat_IN\n"
|
"Language: sat_IN\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "ᱟᱢ ᱪᱮᱫ ᱢᱟᱲᱟᱝ {} ᱨᱮ ᱥᱟᱯᱲᱟᱣ ᱟᱠᱟᱱ ᱞᱚᱠᱟᱞ ᱚᱴᱚᱥᱮᱣ ᱞᱟᱫᱮ ᱥᱟᱱᱟᱢ ᱠᱟᱱᱟ ᱥᱮ?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "ᱨᱤᱪ ᱚᱞ ᱥᱟᱯᱟᱣᱤᱡ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ"
|
msgstr "ᱨᱤᱪ ᱚᱞ ᱥᱟᱯᱟᱣᱤᱡ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮᱸ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "ᱴᱭᱴᱚᱞ"
|
msgstr "ᱴᱭᱴᱚᱞ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "ᱥᱟᱹᱵᱴᱟᱭᱴᱟᱹᱞ, ᱟᱨ ᱵᱟᱝ ᱥᱟᱹᱢᱢᱟᱨᱭ"
|
msgstr "ᱥᱟᱹᱵᱴᱟᱭᱴᱟᱹᱞ, ᱟᱨ ᱵᱟᱝ ᱥᱟᱹᱢᱢᱟᱨᱭ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "ᱟᱢᱟᱜ ᱚᱱᱚᱞ ᱱᱚᱰᱮ ᱚᱞ ᱛᱟᱢ ᱾ ᱪᱤᱱᱦᱟᱹ ᱥᱟᱯᱚᱴ ᱜᱮᱭᱟ ᱾"
|
msgstr "ᱟᱢᱟᱜ ᱚᱱᱚᱞ ᱱᱚᱰᱮ ᱚᱞ ᱛᱟᱢ ᱾ ᱪᱤᱱᱦᱟᱹ ᱥᱟᱯᱯᱚᱴ ᱜᱮᱭᱟ ᱾"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "ᱡᱷᱚᱛᱚ ᱨᱮ {} ᱡᱤᱱᱤᱥ ᱵᱟᱧᱪᱟᱣᱠᱟᱱᱟ"
|
msgstr "ᱡᱷᱚᱛᱚ ᱨᱮ {} ᱡᱤᱱᱤᱥ ᱵᱟᱧᱪᱟᱣᱠᱟᱱᱟ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "ᱴᱮᱜᱽᱥ"
|
msgstr "ᱴᱮᱜᱥ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "ᱞᱚᱭᱥᱮᱱᱥ"
|
msgstr "ᱞᱚᱭᱥᱮᱸᱱᱥ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "ᱢᱚᱭᱞᱟᱹᱴ"
|
msgstr "ᱠᱚᱵᱷᱚᱨ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "ᱱᱚᱶᱟ ᱫᱚ ᱰᱨᱟᱯᱷᱼᱴ ᱠᱟᱱᱟ"
|
msgstr "ᱱᱚᱶᱟ ᱫᱚ ᱰᱨᱟᱯᱷᱼᱴ ᱠᱟᱱᱟ"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "ᱯᱟᱨᱥᱟᱞ"
|
msgstr "ᱯᱟᱨᱥᱟᱞ"
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Sinhala\n"
|
"Language-Team: Sinhala\n"
|
||||||
"Language: si_LK\n"
|
"Language: si_LK\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "මාතෘකාව"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "බලපත්රය"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovak\n"
|
"Language-Team: Slovak\n"
|
||||||
"Language: sk_SK\n"
|
"Language: sk_SK\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "Chceš načítať automaticky uloženú zálohu, s poslednou úpravou {}?"
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Otvor editor formátovaného textu"
|
msgstr "Otvor editor formátovaného textu"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Nadpis"
|
msgstr "Nadpis"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Zhrnutie, alebo podnadpis"
|
msgstr "Zhrnutie, alebo podnadpis"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Tu napíš svoj článok. Markdown je podporovaný."
|
msgstr "Tu napíš svoj článok. Markdown je podporovaný."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Zostáva asi {} znakov"
|
msgstr "Zostáva asi {} znakov"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Štítky"
|
msgstr "Štítky"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licencia"
|
msgstr "Licencia"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Obálka"
|
msgstr "Obálka"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Toto je koncept"
|
msgstr "Toto je koncept"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Zverejniť"
|
msgstr "Zverejniť"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovenian\n"
|
"Language-Team: Slovenian\n"
|
||||||
"Language: sl_SI\n"
|
"Language: sl_SI\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Naslov"
|
msgstr "Naslov"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Oznake"
|
msgstr "Oznake"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licenca"
|
msgstr "Licenca"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "To je osnutek"
|
msgstr "To je osnutek"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Objavi"
|
msgstr "Objavi"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Serbian (Latin)\n"
|
"Language-Team: Serbian (Latin)\n"
|
||||||
"Language: sr_CS\n"
|
"Language: sr_CS\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Otvori uređivač sa stilizacijom"
|
msgstr "Otvori uređivač sa stilizacijom"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Naslov"
|
msgstr "Naslov"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Podnaslov, ili sažetak"
|
msgstr "Podnaslov, ili sažetak"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Napišite vaš članak ovde. Na raspolaganju vam je Markdown."
|
msgstr "Napišite vaš članak ovde. Na raspolaganju vam je Markdown."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Preostalo oko {} znakova"
|
msgstr "Preostalo oko {} znakova"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Markeri"
|
msgstr "Markeri"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licenca"
|
msgstr "Licenca"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Naslovna strana"
|
msgstr "Naslovna strana"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Ovo je nacrt"
|
msgstr "Ovo je nacrt"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Objavi"
|
msgstr "Objavi"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Swedish\n"
|
"Language-Team: Swedish\n"
|
||||||
"Language: sv_SE\n"
|
"Language: sv_SE\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titel"
|
msgstr "Titel"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Skriv din artikel här. Markdown stöds."
|
msgstr "Skriv din artikel här. Markdown stöds."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Ungefär {} karaktärer kvar"
|
msgstr "Ungefär {} karaktärer kvar"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Taggar"
|
msgstr "Taggar"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Licens"
|
msgstr "Licens"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Omslag"
|
msgstr "Omslag"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Publicera"
|
msgstr "Publicera"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Turkish\n"
|
"Language-Team: Turkish\n"
|
||||||
"Language: tr_TR\n"
|
"Language: tr_TR\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Zengin metin editörünü (RTE) aç"
|
msgstr "Zengin metin editörünü (RTE) aç"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Başlık"
|
msgstr "Başlık"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr "Alt başlık, veya açıklama"
|
msgstr "Alt başlık, veya açıklama"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "Makaleni buraya yaz. Markdown kullanabilirsin."
|
msgstr "Makaleni buraya yaz. Markdown kullanabilirsin."
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "Yaklaşık {} karakter kaldı"
|
msgstr "Yaklaşık {} karakter kaldı"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Etiketler"
|
msgstr "Etiketler"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "Lisans"
|
msgstr "Lisans"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "Kapak"
|
msgstr "Kapak"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "Bu bir taslaktır"
|
msgstr "Bu bir taslaktır"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "Yayınla"
|
msgstr "Yayınla"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Ukrainian\n"
|
"Language-Team: Ukrainian\n"
|
||||||
"Language: uk_UA\n"
|
"Language: uk_UA\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: plume\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
|
||||||
"Last-Translator: \n"
|
|
||||||
"Language-Team: Urdu (Pakistan)\n"
|
|
||||||
"Language: ur_PK\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"X-Crowdin-Project: plume\n"
|
|
||||||
"X-Crowdin-Project-ID: 352097\n"
|
|
||||||
"X-Crowdin-Language: ur-PK\n"
|
|
||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
|
||||||
"X-Crowdin-File-ID: 12\n"
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
|
||||||
msgid "Open the rich text editor"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
|
||||||
msgid "Title"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
|
||||||
msgid "Subtitle, or summary"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
|
||||||
msgid "Write your article here. Markdown is supported."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
|
||||||
msgid "Around {} characters left"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
|
||||||
msgid "Tags"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
|
||||||
msgid "License"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
|
||||||
msgid "Cover"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
|
||||||
msgid "This is a draft"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
|
||||||
msgid "Publish"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
+12
-12
@@ -3,7 +3,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:56\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Vietnamese\n"
|
"Language-Team: Vietnamese\n"
|
||||||
"Language: vi_VN\n"
|
"Language: vi_VN\n"
|
||||||
@@ -17,47 +17,47 @@ msgstr ""
|
|||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:282
|
||||||
msgid "Open the rich text editor"
|
msgid "Open the rich text editor"
|
||||||
msgstr "Văn bản của tôi"
|
msgstr "Văn bản của tôi"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
# plume-front/src/editor.rs:315
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Tiêu Châu"
|
msgstr "Tiêu Châu"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
# plume-front/src/editor.rs:319
|
||||||
msgid "Subtitle, or summary"
|
msgid "Subtitle, or summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
+30
-30
@@ -3,61 +3,61 @@ msgstr ""
|
|||||||
"Project-Id-Version: plume\n"
|
"Project-Id-Version: plume\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
|
||||||
"PO-Revision-Date: 2022-01-12 01:20\n"
|
"PO-Revision-Date: 2020-12-19 09:55\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional\n"
|
"Language-Team: Chinese Simplified\n"
|
||||||
"Language: zh_TW\n"
|
"Language: zh_CN\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"X-Crowdin-Project: plume\n"
|
"X-Crowdin-Project: plume\n"
|
||||||
"X-Crowdin-Project-ID: 352097\n"
|
"X-Crowdin-Project-ID: 352097\n"
|
||||||
"X-Crowdin-Language: zh-TW\n"
|
"X-Crowdin-Language: zh-CN\n"
|
||||||
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
|
||||||
"X-Crowdin-File-ID: 12\n"
|
"X-Crowdin-File-ID: 12\n"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:172
|
# plume-front/src/editor.rs:189
|
||||||
msgid "Do you want to load the local autosave last edited at {}?"
|
msgid "Do you want to load the local autosave last edited at {}?"
|
||||||
msgstr "你想要載入上次在 {} 编辑的本地自動保存嗎?"
|
msgstr ""
|
||||||
|
|
||||||
|
# plume-front/src/editor.rs:282
|
||||||
|
msgid "Open the rich text editor"
|
||||||
|
msgstr "打开富文本编辑器"
|
||||||
|
|
||||||
|
# plume-front/src/editor.rs:315
|
||||||
|
msgid "Title"
|
||||||
|
msgstr "标题"
|
||||||
|
|
||||||
|
# plume-front/src/editor.rs:319
|
||||||
|
msgid "Subtitle, or summary"
|
||||||
|
msgstr "副标题或摘要"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:326
|
# plume-front/src/editor.rs:326
|
||||||
msgid "Open the rich text editor"
|
|
||||||
msgstr "開啟 RTF 編輯器"
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:385
|
|
||||||
msgid "Title"
|
|
||||||
msgstr "標題"
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:389
|
|
||||||
msgid "Subtitle, or summary"
|
|
||||||
msgstr "副標題,或摘要"
|
|
||||||
|
|
||||||
# plume-front/src/editor.rs:396
|
|
||||||
msgid "Write your article here. Markdown is supported."
|
msgid "Write your article here. Markdown is supported."
|
||||||
msgstr "在這裡寫下您的文章。支援 Markdown 語法。"
|
msgstr "在这里写下您的文章。支持 Markdown 语法。"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:407
|
# plume-front/src/editor.rs:337
|
||||||
msgid "Around {} characters left"
|
msgid "Around {} characters left"
|
||||||
msgstr "大約還可輸入 {} 字符"
|
msgstr "大约剩余 {} 可输入字符"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:517
|
# plume-front/src/editor.rs:414
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "標籤"
|
msgstr "标签"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:518
|
# plume-front/src/editor.rs:415
|
||||||
msgid "License"
|
msgid "License"
|
||||||
msgstr "授權條款"
|
msgstr "许可协议"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:524
|
# plume-front/src/editor.rs:418
|
||||||
msgid "Cover"
|
msgid "Cover"
|
||||||
msgstr "封面"
|
msgstr "封面"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:564
|
# plume-front/src/editor.rs:438
|
||||||
msgid "This is a draft"
|
msgid "This is a draft"
|
||||||
msgstr "這是草稿"
|
msgstr "这是一个草稿"
|
||||||
|
|
||||||
# plume-front/src/editor.rs:575
|
# plume-front/src/editor.rs:445
|
||||||
msgid "Publish"
|
msgid "Publish"
|
||||||
msgstr "發布"
|
msgstr "发布"
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user