Compare commits

...

581 Commits

Author SHA1 Message Date
Kitaiti Makoto 9a824f06c3 (cargo-release) version {{version}} 2022-05-12 04:29:46 +09:00
Kitaiti Makoto eec09d79fe Fix release.toml 2022-05-12 04:27:48 +09:00
Kitaiti Makoto 79b639c3e6 Update PO files 2022-05-11 03:29:33 +09:00
Kitaiti Makoto efef208f53 Update translations 2022-05-11 03:26:01 +09:00
KitaitiMakoto 27e0f755f6 Merge pull request 'Bump whatlang from 0.15.0 to 0.16.0' (#1078) from whatlang-0.16.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1078
2022-05-10 17:53:35 +00:00
KitaitiMakoto a9d7aae5d6 Merge branch 'main' into whatlang-0.16.0 2022-05-10 17:52:46 +00:00
KitaitiMakoto 42e584a363 Merge pull request 'Add blank line' (#1077) from tiny-change into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1077
2022-05-10 17:50:07 +00:00
Kitaiti Makoto 8c37ea3ec3 Add blank line 2022-05-11 02:35:30 +09:00
dependabot[bot] 3c14fa0058 Bump whatlang from 0.15.0 to 0.16.0
Bumps [whatlang](https://github.com/greyblake/whatlang-rs) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/greyblake/whatlang-rs/releases)
- [Changelog](https://github.com/greyblake/whatlang-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/greyblake/whatlang-rs/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: whatlang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-11 02:33:23 +09:00
dependabot[bot] ab94cca210 Bump tokio from 1.18.1 to 1.18.2
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.18.1 to 1.18.2.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.18.1...tokio-1.18.2)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-09 19:31:15 +00:00
Kitaiti Makoto ea62388985 Update PO files 2022-05-09 23:34:32 +09:00
KitaitiMakoto a9219efee4 Merge pull request 'Move to action area after liking/boosting/commenting' (#1074) from action-id into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1074
2022-05-08 10:24:15 +00:00
Kitaiti Makoto 776ed058c7 [skip ci]Add changelog 2022-05-08 18:53:57 +09:00
Kitaiti Makoto aa3e196b8f Make comment content required 2022-05-08 18:51:09 +09:00
Kitaiti Makoto 52cb7270a9 Set id attributes to action forms in post details page 2022-05-08 18:44:42 +09:00
KitaitiMakoto 66376afb36 Merge pull request 'Upgrade activitystreams to 0.7, again' (#1022) from ap07 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1022
2022-05-08 09:14:53 +00:00
Kitaiti Makoto 96860be1be Fix Follow::accept_follow() 2022-05-08 18:00:42 +09:00
KitaitiMakoto 3bf61efc34 Merge pull request '[skip ci]Update changelog' (#1073) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1073
2022-05-07 16:45:23 +00:00
KitaitiMakoto d95549f58b Merge pull request 'Move local feed before federated feed for non-logged-in users' (#1072) from timeline-order into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1072
2022-05-07 16:35:28 +00:00
Kitaiti Makoto bf24e4878a Merge remote-tracking branch 'origin/main' into ap07 2022-05-07 13:04:47 +09:00
Kitaiti Makoto 9ae231fcef [skip ci]Update changelog 2022-05-07 12:58:47 +09:00
KitaitiMakoto c32acb2fcf Merge pull request 'Sleep between broadcasting' (#1071) from sleep-broadcasting into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1071
2022-05-07 03:50:42 +00:00
Kitaiti Makoto 770c77ee81 Move local feed before federated feed for non-logged-in users 2022-05-07 12:48:34 +09:00
KitaitiMakoto aa3e4d7cf8 Merge branch 'main' into sleep-broadcasting 2022-05-07 03:27:53 +00:00
KitaitiMakoto 156a875f02 Merge pull request 'Move local timeline before federated timeline' (#1070) from timeline-order into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1070
2022-05-07 03:25:01 +00:00
Kitaiti Makoto cfed02bbcf Merge remote-tracking branch 'origin/main' into ap07 2022-05-07 11:15:04 +09:00
Kitaiti Makoto 4d3db9af73 Sleep between broadcasting 2022-05-07 04:50:58 +09:00
Kitaiti Makoto c1c606bc86 [skip ci]Add changelog about timeline order change 2022-05-07 04:46:28 +09:00
Kitaiti Makoto f401949037 Move local timeline before federated timeline 2022-05-07 04:43:00 +09:00
Kitaiti Makoto e8dc0942e5 Merge remote-tracking branch 'origin/main' into ap07 2022-05-06 12:38:47 +09:00
Kitaiti Makoto 9fbafd8e79 Fix Follow object in accepting follow 2022-05-06 12:06:37 +09:00
KitaitiMakoto b9ea83a602 Merge pull request 'More personal timelines' (#1069) from timeline-order into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1069
2022-05-05 19:11:47 +00:00
KitaitiMakoto 2ada5a83af Merge branch 'main' into timeline-order 2022-05-05 19:11:30 +00:00
KitaitiMakoto ec25599d1f Merge pull request 'Fixes #949 Fix time out error on broadcasting' (#1068) from fix-timeout into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1068
2022-05-05 17:15:28 +00:00
KitaitiMakoto 57551610e2 Merge pull request 'Update CircleCI image' (#1066) from update-circleci-image into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1066
2022-05-05 11:29:37 +00:00
Kitaiti Makoto 8948b7acc1 Center timeline tabs 2022-05-05 19:11:23 +09:00
Kitaiti Makoto ccf7ff2bc9 Remove Latest articles from timeline tabs 2022-05-05 19:05:54 +09:00
Kitaiti Makoto 39de967141 Show first timeline at home 2022-05-05 19:03:33 +09:00
Kitaiti Makoto 118cfd7166 Replace hard tabs with soft tabs 2022-05-05 18:14:55 +09:00
Kitaiti Makoto c2fd4ab3a5 Merge remote-tracking branch 'origin/main' into fix-timeout 2022-05-05 17:14:52 +09:00
Kitaiti Makoto 70b5bee00f Move My feed first in timelines 2022-05-05 16:48:51 +09:00
Kitaiti Makoto de605deb1e Don't unwrap 2022-05-05 16:35:03 +09:00
Kitaiti Makoto 116974f811 Add comment about broadcast capacity 2022-05-05 16:29:52 +09:00
Kitaiti Makoto c57f36ccca Merge branch 'fix-timeout' into ap07 2022-05-05 15:50:51 +09:00
Kitaiti Makoto 9def0355aa Reduce broadcast request connections 2022-05-05 15:50:44 +09:00
Kitaiti Makoto 4e833c2061 Follow clippy 2022-05-05 13:14:11 +09:00
Kitaiti Makoto 5871ed7301 Merge branch 'fix-timeout' into ap07 2022-05-05 13:12:04 +09:00
Kitaiti Makoto 97632fdbfe Broadcast asynchronously 2022-05-05 13:03:41 +09:00
Kitaiti Makoto 1f8da7e63d Install futures 2022-05-05 12:41:16 +09:00
Kitaiti Makoto 76ca7c1462 Add futures to plume-common's dependencies 2022-05-05 12:40:37 +09:00
Kitaiti Makoto f06f444a13 Update CircleCI image
See https://discuss.circleci.com/t/legacy-convenience-image-deprecation/41034
2022-05-05 11:32:26 +09:00
Kitaiti Makoto 10dfecf45c Merge remote-tracking branch 'origin/fix-timeout' into ap07 2022-05-05 10:42:03 +09:00
Kitaiti Makoto a7b899817a Run HTTP request in broadcast() on tokio runtime 2022-05-05 09:04:54 +09:00
Kitaiti Makoto e0258003b9 Install tokio and flume 2022-05-05 09:04:34 +09:00
Kitaiti Makoto 2326eb77cd Add tokio to plume-common's dependencies 2022-05-05 08:42:26 +09:00
Kitaiti Makoto 504d41d887 Add flume to plume-common's dependencies 2022-05-05 08:38:57 +09:00
KitaitiMakoto 5a7d5e8099 Merge pull request 'Bump serde_json from 1.0.80 to 1.0.81' (#1064) from serde_json-1.0.81 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1064
2022-05-04 19:56:28 +00:00
KitaitiMakoto 74a1daac8c Merge branch 'main' into serde_json-1.0.81 2022-05-04 19:56:18 +00:00
KitaitiMakoto 1f855601ea Merge pull request 'Bump openssl from 0.10.38 to 0.10.40' (#1063) from openssl-0.10.40 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1063
2022-05-04 19:55:39 +00:00
Kitaiti Makoto f22c4d5c78 Await in consumer 2022-05-05 04:51:42 +09:00
Kitaiti Makoto ce4b216722 Broadcast asynchronously 2022-05-05 04:34:04 +09:00
Kitaiti Makoto 9016995d92 Install tokio 2022-05-05 04:33:52 +09:00
dependabot[bot] 853a1db028 Bump serde_json from 1.0.80 to 1.0.81
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.80 to 1.0.81.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.80...v1.0.81)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-04 19:25:50 +00:00
dependabot[bot] 712ee30a1f Bump openssl from 0.10.38 to 0.10.40
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.38 to 0.10.40.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.38...openssl-v0.10.40)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-04 19:25:29 +00:00
Kitaiti Makoto 9e5f9255d1 Add tokio to plume-common's dependencies 2022-05-05 03:34:11 +09:00
Kitaiti Makoto 2e35441483 Follow reqwest change 2022-05-05 01:21:25 +09:00
Kitaiti Makoto 5c74f598d8 Update Cargo.lock 2022-05-05 01:21:12 +09:00
Kitaiti Makoto 5d711dc47c Upgrade reqwest to 0.11 2022-05-05 01:20:18 +09:00
KitaitiMakoto 9ae3057106 Merge pull request 'Fixes #1061 Render 404 when page not found' (#1062) from render-404 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1062
2022-05-04 12:23:04 +00:00
Kitaiti Makoto b7ea154e51 Render 404 when page not found 2022-05-04 21:21:58 +09:00
Kitaiti Makoto 692e6b1c82 Uninstall tokio 2022-05-04 21:13:30 +09:00
Kitaiti Makoto 528f1bac48 Remove tokio from dependencies 2022-05-04 21:12:52 +09:00
Kitaiti Makoto 35aa2374c4 Execute broadcast synchronously 2022-05-04 21:12:35 +09:00
Kitaiti Makoto 3eb7662aef Log inbox URI when broadcast() failed 2022-05-04 21:04:49 +09:00
Kitaiti Makoto de4fcaee93 Merge remote-tracking branch 'origin/main' into ap07 2022-05-04 05:04:57 +09:00
KitaitiMakoto 812fd3d956 Merge pull request 'Reuse reqwest client on broadcasting' (#1059) from fix-timeout into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1059
2022-05-03 19:59:05 +00:00
KitaitiMakoto 5d3b480790 Merge branch 'main' into fix-timeout 2022-05-03 19:58:38 +00:00
KitaitiMakoto 2f1801acae Merge pull request 'Bump validator from 0.14.0 to 0.15.0' (#1060) from validator-0.15.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1060
2022-05-03 19:57:56 +00:00
Kitaiti Makoto 0404528908 Remove unnecessary clone of config 2022-05-04 04:40:16 +09:00
Kitaiti Makoto 4529b929d8 [skip ci]Add changelog 2022-05-04 04:26:47 +09:00
dependabot[bot] 889decc720 Bump validator from 0.14.0 to 0.15.0
Bumps [validator](https://github.com/Keats/validator) from 0.14.0 to 0.15.0.
- [Release notes](https://github.com/Keats/validator/releases)
- [Changelog](https://github.com/Keats/validator/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Keats/validator/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: validator
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-03 19:24:10 +00:00
Kitaiti Makoto db0f1a3c46 Reuse reqwest client on broadcasting
See https://users.rust-lang.org/t/reqwest-http-client-fails-when-too-much-concurrency/55644/2
2022-05-04 04:23:10 +09:00
KitaitiMakoto ef57ef91f0 Merge pull request 'Fixes #1051 Fix accept header' (#1058) from activitystreams-content-type into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1058
2022-05-03 17:21:25 +00:00
Kitaiti Makoto 073b72c9ed Add more fixes 2022-05-04 02:01:58 +09:00
Kitaiti Makoto 45a6744d4d [skip ci]Add changelog 2022-05-04 01:58:44 +09:00
Kitaiti Makoto 4d19861a25 Fix accept header 2022-05-04 01:56:49 +09:00
Kitaiti Makoto f5906cacf3 Restore missing logic for Media 2022-05-03 22:25:43 +09:00
Kitaiti Makoto 03ba77a577 Restore filter 2022-05-03 12:36:31 +09:00
Kitaiti Makoto 0fc7372781 Restore order of decl of boundary of broadcast() 2022-05-03 12:34:56 +09:00
Kitaiti Makoto 6c2846980a Merge remote-tracking branch 'origin/main' into ap07 2022-05-03 12:24:04 +09:00
Kitaiti Makoto 0685c59bf3 Add changelog 2022-05-03 12:18:52 +09:00
KitaitiMakoto 5f629195f8 Merge pull request 'Bump whatlang from 0.13.0 to 0.15.0' (#1057) from whatlang-0.15.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1057
2022-05-03 03:08:09 +00:00
KitaitiMakoto 13eeedb620 Merge branch 'main' into whatlang-0.15.0 2022-05-03 02:49:51 +00:00
KitaitiMakoto a076c132ca Merge pull request 'Bump serde from 1.0.136 to 1.0.137' (#1056) from serde-1.0.137 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1056
2022-05-03 02:49:09 +00:00
KitaitiMakoto a76e0dfe5b Merge branch 'main' into serde-1.0.137 2022-05-03 02:27:46 +00:00
KitaitiMakoto 8faac20977 Merge pull request 'Bump serde_json from 1.0.79 to 1.0.80' (#1055) from serde_json-1.0.80 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1055
2022-05-03 02:22:54 +00:00
Kitaiti Makoto b04edfa05e Follow clippy 2022-05-03 10:53:16 +09:00
Kitaiti Makoto 1f62bf27f8 Fix nest of source property for Post 2022-05-03 10:47:21 +09:00
Kitaiti Makoto 1e0d1fb97a Add test for CustomGroup 2022-05-03 10:16:57 +09:00
Kitaiti Makoto 19d30c12d1 Parse source property properly 2022-05-03 10:04:22 +09:00
Kitaiti Makoto 384474930c Add test for Create activity with licensed article 2022-05-03 08:24:22 +09:00
dependabot[bot] 4cb64e0a8c Bump whatlang from 0.13.0 to 0.15.0
Bumps [whatlang](https://github.com/greyblake/whatlang-rs) from 0.13.0 to 0.15.0.
- [Release notes](https://github.com/greyblake/whatlang-rs/releases)
- [Changelog](https://github.com/greyblake/whatlang-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/greyblake/whatlang-rs/compare/v0.13.0...v0.15.0)

---
updated-dependencies:
- dependency-name: whatlang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 19:35:35 +00:00
dependabot[bot] cd2a2df48d Bump serde from 1.0.136 to 1.0.137
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.136 to 1.0.137.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.136...v1.0.137)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 19:35:19 +00:00
dependabot[bot] cbf960500b Bump serde_json from 1.0.79 to 1.0.80
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.79 to 1.0.80.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.79...v1.0.80)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 19:35:06 +00:00
Kitaiti Makoto 3d434f1923 Remove old activitypub related crates 2022-05-03 03:05:49 +09:00
Kitaiti Makoto 52022fb597 Remove activitypub crate from plume-models 2022-05-03 02:54:08 +09:00
Kitaiti Makoto d96940c848 Don't implement activitypub::Link for Id 2022-05-03 02:51:36 +09:00
Kitaiti Makoto b17884681d Implement ap_followers using activitystreams 2022-05-03 02:50:14 +09:00
Kitaiti Makoto 78a001ac89 Remove trailing 07 in routes/posts.rs 2022-05-03 02:14:23 +09:00
Kitaiti Makoto 9b04fb96e6 Remote trailing 07 in inbox.rs 2022-05-03 02:12:59 +09:00
Kitaiti Makoto d23002b817 Remove trailing 07 from method name 2022-05-03 02:11:46 +09:00
Kitaiti Makoto 6282b98b03 Fix doc test 2022-05-03 02:05:46 +09:00
Kitaiti Makoto d75600ba14 Remove trailing 07 in activity_pub/inbox.rs 2022-05-03 01:32:47 +09:00
Kitaiti Makoto e6ea302319 Remove activitypub crate from Inbox test 2022-05-03 01:31:39 +09:00
Kitaiti Makoto df005a28f8 Rename: activity07() -> activity() 2022-05-03 01:26:15 +09:00
Kitaiti Makoto 15134eed60 Rename: get_sender07() -> get_sender() 2022-05-03 01:24:22 +09:00
Kitaiti Makoto 7dd56a71e3 Rename: from_db07() -> from_db() 2022-05-03 01:23:37 +09:00
Kitaiti Makoto 06d2f68ecd Rename: from_activity07() -> from_activity() 2022-05-03 01:22:55 +09:00
Kitaiti Makoto 9a640b3438 Rename: deref07() -> deref() 2022-05-03 01:22:02 +09:00
Kitaiti Makoto 9791607793 Rename: with07() -> with() 2022-05-03 01:20:40 +09:00
Kitaiti Makoto ccd3c8a3f2 Don't implement activitypub's Object for Source 2022-05-03 01:17:27 +09:00
Kitaiti Makoto 6bbadc78b0 Rename: Licensed07 -> Licensed 2022-05-03 01:15:13 +09:00
Kitaiti Makoto e1673787b4 Remove unused Licensed struct 2022-05-03 01:14:29 +09:00
Kitaiti Makoto 1dd176dd80 Rename: broadcast07() -> broadcast() 2022-05-03 01:12:39 +09:00
Kitaiti Makoto 5c59687cb8 Remove unused broadcast() 2022-05-03 01:12:02 +09:00
Kitaiti Makoto a24e3c46e6 Remove trailing 07 in posts.rs 2022-05-03 01:07:52 +09:00
Kitaiti Makoto 267fecba66 Remove unsed posts::LicensedArticle 2022-05-03 01:03:54 +09:00
Kitaiti Makoto fc99d2b7a0 Remove trailing 07 in remote_fetch_actor.rs 2022-05-03 00:57:19 +09:00
Kitaiti Makoto 1b32fa1e34 Remove unused Media::from_activity() 2022-05-03 00:54:37 +09:00
Kitaiti Makoto ce42524273 Remote trailng 07 from Note in comments.rs 2022-05-03 00:50:43 +09:00
Kitaiti Makoto 595fa05660 Use Follow::to_activity07() instead of to_activity() 2022-05-03 00:47:46 +09:00
Kitaiti Makoto f8a0dff526 Remove unused Follow::build_accept() 2022-05-03 00:45:22 +09:00
Kitaiti Makoto 06d216c7ed Remove unused Follow::accept_follow() 2022-05-03 00:43:47 +09:00
Kitaiti Makoto f44bca30f4 Use Follow::build_undo07() instead of build_undo() 2022-05-03 00:41:05 +09:00
Kitaiti Makoto e4180b3b38 Rename: ApSignature07 -> ApSignature 2022-05-03 00:29:44 +09:00
Kitaiti Makoto 992a482b96 Remove unused ApSignature type 2022-05-03 00:26:37 +09:00
Kitaiti Makoto 0ad845e0f7 Remove unused User::fetch_outbox() 2022-05-03 00:22:22 +09:00
Kitaiti Makoto ee97213c90 Remove trailing 07 from import of OrderedCollectionPage 2022-05-03 00:18:42 +09:00
Kitaiti Makoto 6d919da049 Remove duplicate import 2022-05-03 00:17:19 +09:00
Kitaiti Makoto 6c615d01ad Remove users::CustomPerson 2022-05-03 00:15:46 +09:00
Kitaiti Makoto f8870af9fe Remove trailing 07 from Hashtag 2022-05-03 00:10:51 +09:00
Kitaiti Makoto 2fe2505a01 Remove unused Hashtag 2022-05-03 00:09:15 +09:00
Kitaiti Makoto e41fa353e4 Use User::to_activity07() instead of to_activity() 2022-05-03 00:06:18 +09:00
Kitaiti Makoto effdc44943 Use User::delete_activity07() instead of delete_activity() 2022-05-03 00:03:11 +09:00
Kitaiti Makoto fd341bdb22 Use User::outbox07() instead of outbox() 2022-05-02 23:59:38 +09:00
Kitaiti Makoto 68c794c54b Use User::outbox_page07() instead of outbox_page() 2022-05-02 23:55:18 +09:00
Kitaiti Makoto 7b3b00be23 Remove unused Tag::from_activity() and to_activity() 2022-05-02 23:51:32 +09:00
Kitaiti Makoto 41ccacc5d3 Remove unused Mention::from_activity() 2022-05-02 23:47:36 +09:00
Kitaiti Makoto 4ef9350ce7 Remove unused Mention::to_activity() 2022-05-02 23:45:06 +09:00
Kitaiti Makoto 5d08ff6c3b Use Mention::build_activity07() instead of build_activity() 2022-05-02 23:43:24 +09:00
Kitaiti Makoto 01dca62ce5 Rename: Like07 -> LikeAct 2022-05-02 23:38:32 +09:00
Kitaiti Makoto 6ab1ecd57b Use Like::to_activity07() instead of to_activity() 2022-05-02 23:37:58 +09:00
Kitaiti Makoto b13444895f Use Like::build_undo07() instead of build_undo() 2022-05-02 23:34:19 +09:00
Kitaiti Makoto 771c157fe5 Use Comment::to_activity07() instead of to_activity() 2022-05-02 23:30:44 +09:00
Kitaiti Makoto b5e1076b0e Use Comment::build_delete07() instead of build_delete() 2022-05-02 23:26:25 +09:00
Kitaiti Makoto 6cc43c2420 Use Comment::create_activity07() instead of create_activity() 2022-05-02 23:23:28 +09:00
Kitaiti Makoto f365041a45 Use Reshare::to_activity07() instead of to_activity() 2022-05-02 23:19:29 +09:00
Kitaiti Makoto ae9c9262f7 Use Reshare::build_undo07() instead of build_undo() 2022-05-02 23:16:55 +09:00
Kitaiti Makoto 40ce515e6c Don't rename activitystreams' tokens to 07 2022-05-02 23:11:48 +09:00
Kitaiti Makoto bc96af7f5f Remove unused blogs::CustomGroup 2022-05-02 23:08:41 +09:00
Kitaiti Makoto 811c20c8fb Use Blog::to_activity07() instead of to_activity() 2022-05-02 23:07:05 +09:00
Kitaiti Makoto 4b4c22cf8a Use Blog::outbox_page07() instead of outbox_page() 2022-05-02 23:01:26 +09:00
Kitaiti Makoto 0524b0b153 Add Blog::outbox_page07() 2022-05-02 22:56:54 +09:00
Kitaiti Makoto cd6c57b9c5 Use Blog::outbox07() instead of outbox() 2022-05-02 22:55:06 +09:00
Kitaiti Makoto f608f7a4d6 Install activitystreams 2022-05-02 22:55:06 +09:00
Kitaiti Makoto 803680186b Add Blog::outbox07() 2022-05-02 22:55:06 +09:00
Kitaiti Makoto 68a01d5f9b Add activitystreams to Plume's dependencies 2022-05-02 22:55:06 +09:00
Kitaiti Makoto 5b3a472b66 Use Post::to_activity07() instead of to_activity() 2022-05-02 22:55:06 +09:00
Kitaiti Makoto 2a85f775e9 Use Post::build_delete07() instead of build_delete() 2022-05-02 22:55:06 +09:00
Kitaiti Makoto a958300a58 Use Post::update_hashtags07() instead of update_hashtags() 2022-05-02 22:55:03 +09:00
Kitaiti Makoto a8be31b177 Use Post::update_tags07() instead of update_tags() 2022-05-02 22:26:59 +09:00
Kitaiti Makoto 6cd68ab8b0 Use Post::update_activity07() instead of update_activity() 2022-05-02 22:23:12 +09:00
Kitaiti Makoto a589435f4f Use Post::create_activity07() instead of create_activity() 2022-05-02 22:19:00 +09:00
Kitaiti Makoto 39b49c707e Use Post::update_mentions07() instead of update_mentions() 2022-05-02 21:49:00 +09:00
Kitaiti Makoto c4bb1f771b Add test for Tag::from_activity07() 2022-05-02 21:00:44 +09:00
Kitaiti Makoto 28440271bb Rename: FromId::from_id07 -> from_id 2022-05-02 19:24:36 +09:00
Kitaiti Makoto 0ab7774e29 Rename: AsObject07 -> AsObject 2022-05-02 17:43:03 +09:00
Kitaiti Makoto 33afe9111e Remove AsObject 2022-05-02 17:38:08 +09:00
Kitaiti Makoto d8a2e1925f Rename FromId07 -> FromId 2022-05-02 16:07:08 +09:00
Kitaiti Makoto 2804f44a06 Rmove FromId 2022-05-02 12:58:01 +09:00
Kitaiti Makoto 2165c286ae Remove with() 2022-05-01 19:49:12 +09:00
Kitaiti Makoto 8cbf410faf Remove execute permission from plume-common/src/lib.rs 2022-05-01 19:15:16 +09:00
Kitaiti Makoto c521a81373 Make test follow LicensedArticle change 2022-05-01 19:13:38 +09:00
Kitaiti Makoto 7ade0550c9 Remove unused import 2022-05-01 18:54:11 +09:00
Kitaiti Makoto 41bc2d6949 Make LicensedArticle's license fieald optional 2022-05-01 18:53:51 +09:00
Kitaiti Makoto de6e9c0e2e Fix Post::from_activity07() 2022-05-01 13:00:04 +09:00
Kitaiti Makoto 38ebc9ea41 Modify test data for Post 2022-05-01 12:59:48 +09:00
Kitaiti Makoto 8f976be998 Implement AsObject07 for PostUpdate 2022-05-01 09:56:16 +09:00
Kitaiti Makoto e5a2850105 Implement FromId07 for PostUpdate 2022-05-01 09:56:03 +09:00
KitaitiMakoto 85727c6d4c Merge pull request 'Bump ldap3 from 0.10.3 to 0.10.4' (#1054) from ldap3-0.10.4 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1054
2022-04-30 23:35:00 +00:00
KitaitiMakoto 87247a23b3 Merge branch 'main' into ldap3-0.10.4 2022-04-30 23:08:53 +00:00
KitaitiMakoto 61785364e3 Merge pull request 'Bump ctrlc from 3.2.1 to 3.2.2' (#1053) from ctrlc-3.2.2 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1053
2022-04-30 23:08:30 +00:00
Kitaiti Makoto 76f688c967 Replace some Inbox::with with with07 2022-05-01 07:45:42 +09:00
Kitaiti Makoto 05df3b89a1 Fix Follow::activity07() 2022-05-01 07:45:05 +09:00
Kitaiti Makoto 4e42a34337 Replace some with() with with07() 2022-05-01 06:05:20 +09:00
Kitaiti Makoto 62372201e0 Fix inbox::tests::create_post() 2022-05-01 06:04:58 +09:00
Kitaiti Makoto 036913a828 Use id() for reply_tos 2022-05-01 04:45:56 +09:00
dependabot[bot] b2a889b9e4 Bump ldap3 from 0.10.3 to 0.10.4
Bumps [ldap3](https://github.com/inejge/ldap3) from 0.10.3 to 0.10.4.
- [Release notes](https://github.com/inejge/ldap3/releases)
- [Changelog](https://github.com/inejge/ldap3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inejge/ldap3/compare/v0.10.3...v0.10.4)

---
updated-dependencies:
- dependency-name: ldap3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-26 19:28:07 +00:00
dependabot[bot] fae8338772 Bump ctrlc from 3.2.1 to 3.2.2
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.2.1...3.2.2)

---
updated-dependencies:
- dependency-name: ctrlc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 19:25:52 +00:00
Kitaiti Makoto 79b5d9a690 Replace Inbox::with() with with07() 2022-04-24 07:25:31 +09:00
Kitaiti Makoto 3e54d10981 Implement AsObject07<User, Undo07, &DbConn> for Like 2022-04-24 07:11:46 +09:00
Kitaiti Makoto a1c3bfb646 Implement FromId07 for Like 2022-04-24 07:10:56 +09:00
Kitaiti Makoto b2528c21ff Implement AsObject07<User, Like07, &DbConn> for Post 2022-04-24 07:04:30 +09:00
Kitaiti Makoto fcc9e1d81b Implement Like::build_undo07() 2022-04-24 07:03:13 +09:00
Kitaiti Makoto 3093f713ef Add test for Like::build_undo07() 2022-04-24 07:03:06 +09:00
Kitaiti Makoto 4ea29d29a0 Implement Like::to_activity07() 2022-04-24 06:58:33 +09:00
Kitaiti Makoto 6b8d90d8b6 Add test for Like::to_activity07() 2022-04-24 06:58:17 +09:00
Kitaiti Makoto bd3e6a5a91 Replace some Inbox::with with with07 2022-04-24 06:54:07 +09:00
Kitaiti Makoto 46f4676efb Implement Reshare::build_undo07() 2022-04-24 06:48:31 +09:00
Kitaiti Makoto c814ac5681 Add test for Reshare::build_undo07() 2022-04-24 06:48:16 +09:00
Kitaiti Makoto 0887399048 Implement AsObject07<User, Undo07, &DbConn> for Reshare 2022-04-24 06:42:48 +09:00
Kitaiti Makoto f2a2bf2b23 Implement FromId07 for Reshare 2022-04-24 06:41:21 +09:00
Kitaiti Makoto e2702a187b Implement AsObject07<User, Announce07, &DbConn> for Post 2022-04-24 06:35:50 +09:00
Kitaiti Makoto d78a57ce47 Implement Reshare::to_ativity07() 2022-04-24 06:34:00 +09:00
Kitaiti Makoto 10acbdd41f Add test for Reshare::to_activity07() 2022-04-24 06:33:39 +09:00
Kitaiti Makoto 73009818f2 Implement AsObject07<User, Undo07, &DbConn> for Follow 2022-04-24 06:11:54 +09:00
Kitaiti Makoto fb5027becd Implement FromId07 for Follow 2022-04-24 06:09:00 +09:00
Kitaiti Makoto 86609b51fa Implement AsObject07<User, FollowAct07, &DbConn> for User 2022-04-24 05:51:24 +09:00
Kitaiti Makoto 44799e94fd Implement Follow::accept_follow07() 2022-04-24 05:50:45 +09:00
Kitaiti Makoto f14c307786 Remove unused type parameter from broadcast07() 2022-04-24 05:47:11 +09:00
Kitaiti Makoto 174624f5c1 Implement Follow::build_undo07() 2022-04-24 03:45:22 +09:00
Kitaiti Makoto 5f91345d69 Add test for Follow::build_undo07() 2022-04-24 03:45:11 +09:00
Kitaiti Makoto 9ca975113c Implement Follow::build_accept07() 2022-04-24 03:39:08 +09:00
Kitaiti Makoto 38a55857c6 Add test for Follow::build_accept07() 2022-04-24 03:38:54 +09:00
Kitaiti Makoto 9343d3a120 Implement Follow::to_activity07() 2022-04-24 03:38:36 +09:00
Kitaiti Makoto c5656971c9 Add test for Follow::to_activity07() 2022-04-24 03:38:24 +09:00
Kitaiti Makoto ed55b66253 Implement AsObject07 for Comment 2022-04-24 01:49:27 +09:00
Kitaiti Makoto 713ffb9506 Fix Comment::create_activity07() 2022-04-24 01:37:30 +09:00
Kitaiti Makoto 9969e844ca Add test for Comment self federation 2022-04-24 01:37:03 +09:00
Kitaiti Makoto 0c61dca9ca Follow clippy 2022-04-23 22:49:13 +09:00
Kitaiti Makoto 957725fbf8 impl FromId07<DbConn> for Comment 2022-04-23 22:46:49 +09:00
Kitaiti Makoto 1f6361a9a2 Fix Cargo.toml 2022-04-18 00:21:17 +09:00
Kitaiti Makoto cf870971d1 Add test for Comment::build_delete07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 08ac7227b5 Implement Comment::builde_delete07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 88eb61c320 Implement Comment::create_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 1c1dbd481a Add test for Comment::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 86b4f622ea Implement Comment::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto f854bc5838 Add test for LicensedArticle deserialization 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 489156f4a3 Add test for Post's self federation 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 01e8b0bce8 Implement AsObject for Post 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 9183d04e66 Fix Post::from_activity07() for borrow checker 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 5e463e2cc9 Implement FromId07 for Post 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 6e2bff10f7 Add test for Post::build_delete07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 3e9d9a81b7 Implement Mention::build_delete07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 98e0754976 Add test for Post::build_delete() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto da7870eeba Implement Post::update_hashtags07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto c1562f3868 Implement Post::update_tags07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto e0390cb105 Implement Post::update_mentions07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 32cd91cfb9 Implement Mention::from_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 991dfccf3b Add test for Post::update_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 16e012ba00 Implement Post::update_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 871618f45d Add test for Post::create_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 680d321a2e Implement Post::create_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto c37ff54857 Fix Post::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto d4018d61d4 Add test for Post::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 21a0059755 Follow clippy 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 05f4c186f4 Fix test for LicensedArticle serialization 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 53512a6167 Fix SourceProperty property 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 7cf7700ef7 Implement Post::to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 216855d3a7 Add SourceProperty to LicensedArticle 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 23f273e5e8 Readd assert-json-diff 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 70949fad02 Rename: ActorSource -> SourceProperty 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 1f5ce8e504 Add test for Mention::build_activity07() and to_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 2316d36e03 Add test for Tag::from_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 2b1ddc71ac Implement Tag::to_activity07() and Tag::build_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto b9dac1a21a Define Hashtag07 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 95fb5a3c71 Implement Media::from_activity07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 75b43a738f Follow clippy 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 5bd467c4c1 Remove unnecessary records 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 74d6dc5089 Implement Blog::to_activity07(), outbox_collection07() and outbox_collection_page07() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 994a4dbb2d Add source property to CustomGroup 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 67996cc938 Add test for Blog::outbox_collection_page() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto f5e776c4d7 Fix first and last link in Blog::outbox_collection() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto e27fc47287 Extract Blog::outbox_collection_page() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 0ed91b89ff Add test for Blog::outbox_collection() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 00862790a1 Extract Blog::outbox_collection() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto ab6f39c192 Add test for Blog::to_activity() 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 4edc201c14 Implement FromId07 for Blog 2022-04-18 00:19:33 +09:00
Kitaiti Makoto a1a7acfe94 Use new activitystreams APIs 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 6b5a1d2130 Use Base::retract() instead of into_any_base() on creating activity 2022-04-18 00:19:33 +09:00
Kitaiti Makoto 85e35fdb5d Update activitystreams 2022-04-18 00:19:33 +09:00
Kitaiti Makoto da9e13622c Use Inbox::with07() for User, Delete, User 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 8f4dd8a57b Implement User::fetch_outbox07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 78b0535063 Implement User::fetch_outbox_page07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 6323c7aef8 Add test for User::outbox_page_collection07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 7f0ad56d07 Implement User::outbox_collection_page07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e7eea3901f Implement User::outbox_page07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 0979471e54 Add test for User::delete_activity07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 8d69051a61 Implement User::delete_activity07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 55ca1345e1 Add test self_federation07() for User 2022-04-18 00:11:12 +09:00
Kitaiti Makoto ad951ca842 Add test for User::to_activity07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto cb8e2e9294 Implement User::to_activity() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 038d65acaa Implement User::outbox07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e392a89526 Add test for User::outbox_collection07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto d62f51665b Implement User::outbox_collection07() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e42aa6fe8e Implement From<iri_string::validate::Error> for Error 2022-04-18 00:11:12 +09:00
Kitaiti Makoto ab126563f3 Implement AsObject07 for User 2022-04-18 00:11:12 +09:00
Kitaiti Makoto c1b9ebdae6 [REFACTORING]Reduce duplicated closure 2022-04-18 00:11:12 +09:00
Kitaiti Makoto d3e11c78d7 [REFACTORING]Use method chain instead of if clauses 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 4ccfec8019 Use OneOrMany<AnyBase>::to_as_uri() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto bb5157637d Implement OneOrMany<AnyBase>::to_as_uri() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 456df3e535 Use OneOrMany<&AnyString>::as_as_str() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto f0112850fa Implement OneOrMany<&AnyString>::as_as_str() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto a6a21d5dfa Rewrite to_as_string() using method chain instead of if expressions 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 249fbbe891 Remove unused import 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e925865767 Use &AnyString::as_as_str() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 28643fc2c2 Implement &AnyString::as_as_str() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 3db10a09bb Use OneOrMany<&AnyString>::to_as_string() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto a80a95d471 Implement OneOrMany<&AnyString>::to_as_string() 2022-04-18 00:11:12 +09:00
Kitaiti Makoto e407d58ee9 Implement FromId07 for User 2022-04-18 00:11:12 +09:00
Kitaiti Makoto a6d839a766 Make fields of ApSignature07 and PublicKey07 public 2022-04-18 00:11:12 +09:00
Kitaiti Makoto f3b67ab6c9 WIP 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 66f5628a27 Add suffix 07 to activitystreams 0.7 related methods 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 4b3b5c1f40 Implement From<activitystreams::checked::CheckError> for Error 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 3e687f3af0 Reduce type parameter from broadcast07 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 119d3e4f6a [plume-common]Add tests for new ActivityPub functions 2022-04-18 00:11:12 +09:00
Kitaiti Makoto a21d66178e [plume-common]Implement ActivityPub related function using activitystreams 0.7 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 52967f3e47 [plume-common]Implement ActivityPub-related code using activitystreams 0.7 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 29439f9d02 Add tests for newly added ActivityPub-related structs 2022-04-18 00:11:12 +09:00
Kitaiti Makoto bc72a4c2d1 Install assert-json-diff 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 3ded0e2166 Add assert-json-diff to plume-common's dependencies 2022-04-18 00:11:12 +09:00
Kitaiti Makoto 27c10e5e5c Install activitystreams-ext 2022-04-18 00:10:41 +09:00
Kitaiti Makoto 816aefe72a Add ActivityStreams Ext to plume-common dependencies 2022-04-18 00:10:41 +09:00
Kitaiti Makoto bff50f8e4c Add activitystreams 0.7 to plume-models dependencies 2022-04-18 00:10:41 +09:00
Kitaiti Makoto be1c22815b Install activitystreams 0.7 2022-04-18 00:10:19 +09:00
Kitaiti Makoto 08ab7ffd08 Add activitystreams 0.7 to plume-common dependencies 2022-04-17 23:47:13 +09:00
KitaitiMakoto 8709f6cf9f Merge pull request 'Bump tracing from 0.1.32 to 0.1.34' (#1050) from tracing-0.1.34 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1050
2022-04-17 14:44:46 +00:00
KitaitiMakoto 04cae95635 Merge branch 'main' into tracing-0.1.34 2022-04-17 14:44:27 +00:00
KitaitiMakoto 5d37b2534a Merge pull request 'Bump wasm-bindgen from 0.2.78 to 0.2.80' (#1049) from wasm-bindgen-0.2.80 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1049
2022-04-17 14:44:00 +00:00
dependabot[bot] eafafdaadf Bump wasm-bindgen from 0.2.78 to 0.2.80
Bumps [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) from 0.2.78 to 0.2.80.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/compare/0.2.78...0.2.80)

---
updated-dependencies:
- dependency-name: wasm-bindgen
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-17 23:43:25 +09:00
KitaitiMakoto 6897b8fa58 Merge pull request 'Bump js-sys from 0.3.55 to 0.3.57' (#1048) from js-sys-0.3.57 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1048
2022-04-17 14:40:47 +00:00
dependabot[bot] 16d3279d72 Bump js-sys from 0.3.55 to 0.3.57
Bumps [js-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.55 to 0.3.57.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: js-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-17 23:40:01 +09:00
KitaitiMakoto 36c76c534d Merge pull request 'Bump web-sys from 0.3.55 to 0.3.57' (#1047) from web-sys-0.3.57 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1047
2022-04-17 14:39:18 +00:00
KitaitiMakoto 26f460be89 Merge branch 'main' into web-sys-0.3.57 2022-04-17 14:39:05 +00:00
KitaitiMakoto b9fb13104a Merge pull request 'Bump ammonia from 3.1.4 to 3.2.0' (#1046) from ammonia-3.2.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1046
2022-04-17 14:38:08 +00:00
dependabot[bot] 5cc411158f Bump tracing from 0.1.32 to 0.1.34
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.32 to 0.1.34.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.32...tracing-0.1.34)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-15 19:29:35 +00:00
dependabot[bot] ca69c93531 Bump web-sys from 0.3.55 to 0.3.57
Bumps [web-sys](https://github.com/rustwasm/wasm-bindgen) from 0.3.55 to 0.3.57.
- [Release notes](https://github.com/rustwasm/wasm-bindgen/releases)
- [Changelog](https://github.com/rustwasm/wasm-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustwasm/wasm-bindgen/commits)

---
updated-dependencies:
- dependency-name: web-sys
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 19:48:13 +00:00
dependabot[bot] 95cb7cc904 Bump ammonia from 3.1.4 to 3.2.0
Bumps [ammonia](https://github.com/rust-ammonia/ammonia) from 3.1.4 to 3.2.0.
- [Release notes](https://github.com/rust-ammonia/ammonia/releases)
- [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-ammonia/ammonia/compare/v3.1.4...v3.2.0)

---
updated-dependencies:
- dependency-name: ammonia
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 19:48:04 +00:00
KitaitiMakoto 0a62fa46aa Merge pull request 'Bump tracing-subscriber from 0.3.9 to 0.3.10' (#1045) from tracing-subscriber-0.3.10 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1045
2022-04-03 13:28:51 +00:00
KitaitiMakoto 2e60410969 Merge branch 'main' into tracing-subscriber-0.3.10 2022-04-03 13:28:09 +00:00
KitaitiMakoto a12d3a591b Merge pull request 'Bump ldap3 from 0.10.2 to 0.10.3' (#1044) from ldap3-0.10.3 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1044
2022-04-03 13:27:42 +00:00
dependabot[bot] 3cf7c67b6d Bump tracing-subscriber from 0.3.9 to 0.3.10
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.9 to 0.3.10.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.9...tracing-subscriber-0.3.10)

---
updated-dependencies:
- dependency-name: tracing-subscriber
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 19:26:25 +00:00
dependabot[bot] 2cf79f31b7 Bump ldap3 from 0.10.2 to 0.10.3
Bumps [ldap3](https://github.com/inejge/ldap3) from 0.10.2 to 0.10.3.
- [Release notes](https://github.com/inejge/ldap3/releases)
- [Changelog](https://github.com/inejge/ldap3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inejge/ldap3/compare/v0.10.2...v0.10.3)

---
updated-dependencies:
- dependency-name: ldap3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-30 19:30:55 +00:00
KitaitiMakoto 24cf941303 Merge pull request 'Bump native-tls from 0.2.8 to 0.2.10' (#1043) from native-tls-0.2.10 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1043
2022-03-30 19:04:42 +00:00
dependabot[bot] 38cf9b5496 Bump native-tls from 0.2.8 to 0.2.10
Bumps [native-tls](https://github.com/sfackler/rust-native-tls) from 0.2.8 to 0.2.10.
- [Release notes](https://github.com/sfackler/rust-native-tls/releases)
- [Changelog](https://github.com/sfackler/rust-native-tls/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sfackler/rust-native-tls/compare/v0.2.8...v0.2.10)

---
updated-dependencies:
- dependency-name: native-tls
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-29 19:29:35 +00:00
KitaitiMakoto b9607b32ac Merge pull request 'Bump rsass from 0.23.4 to 0.24.0' (#1042) from rsass-0.24.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1042
2022-03-26 15:10:16 +00:00
KitaitiMakoto 3393da2560 Merge pull request 'Bump bcrypt from 0.12.0 to 0.12.1' (#1041) from bcrypt-0.12.1 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1041
2022-03-26 15:09:54 +00:00
dependabot[bot] 7566f94690 Bump rsass from 0.23.4 to 0.24.0
Bumps [rsass](https://github.com/kaj/rsass) from 0.23.4 to 0.24.0.
- [Release notes](https://github.com/kaj/rsass/releases)
- [Changelog](https://github.com/kaj/rsass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/rsass/compare/v0.23.4...v0.24.0)

---
updated-dependencies:
- dependency-name: rsass
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-24 19:29:35 +00:00
dependabot[bot] ee8312fb57 Bump bcrypt from 0.12.0 to 0.12.1
Bumps [bcrypt](https://github.com/Keats/rust-bcrypt) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/Keats/rust-bcrypt/releases)
- [Commits](https://github.com/Keats/rust-bcrypt/compare/v0.12.0...v0.12.1)

---
updated-dependencies:
- dependency-name: bcrypt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-21 19:32:31 +00:00
KitaitiMakoto 76219704f3 Merge pull request 'Bump rpassword from 5.0.1 to 6.0.1' (#1040) from rpassword-6.0.1 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1040
2022-03-20 20:29:35 +00:00
dependabot[bot] 2a43d4e88a Bump rpassword from 5.0.1 to 6.0.1
Bumps [rpassword](https://github.com/conradkleinespel/rpassword) from 5.0.1 to 6.0.1.
- [Release notes](https://github.com/conradkleinespel/rpassword/releases)
- [Commits](https://github.com/conradkleinespel/rpassword/compare/v5.0.1...v6.0.1)

---
updated-dependencies:
- dependency-name: rpassword
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-14 19:29:46 +00:00
KitaitiMakoto f0ce073a37 Merge pull request 'Bump tracing from 0.1.31 to 0.1.32' (#1039) from tracing-0.1.32 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1039
2022-03-12 13:46:19 +00:00
dependabot[bot] 8047196394 Bump tracing from 0.1.31 to 0.1.32
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.31 to 0.1.32.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.31...tracing-0.1.32)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-09 19:30:07 +00:00
KitaitiMakoto 3cf52b3985 Merge pull request 'Add test for Tag::from_activity()' (#1037) from tag-test into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1037
2022-03-06 11:44:02 +00:00
KitaitiMakoto ac378e448b Merge pull request 'Bump once_cell from 1.9.0 to 1.10.0' (#1038) from once_cell-1.10.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1038
2022-03-06 11:43:40 +00:00
Kitaiti Makoto daae2038f8 Add test for Tag::from_activity() 2022-03-06 20:22:36 +09:00
dependabot[bot] a2356c6e59 Bump once_cell from 1.9.0 to 1.10.0
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/matklad/once_cell/releases)
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-03 19:31:09 +00:00
KitaitiMakoto 44b91c6f07 Merge pull request 'Update crates' (#1036) from ldap3-0.10.2 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1036
2022-03-01 13:30:25 +00:00
Kitaiti Makoto 144565d13e Merge remote-tracking branch 'github/dependabot/cargo/bcrypt-0.12.0' into ldap3-0.10.2 2022-03-01 22:06:38 +09:00
dependabot[bot] 07fd66863d Bump bcrypt from 0.11.0 to 0.12.0
Bumps [bcrypt](https://github.com/Keats/rust-bcrypt) from 0.11.0 to 0.12.0.
- [Release notes](https://github.com/Keats/rust-bcrypt/releases)
- [Commits](https://github.com/Keats/rust-bcrypt/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: bcrypt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-28 19:46:44 +00:00
dependabot[bot] 385a5f7c33 Bump ldap3 from 0.9.3 to 0.10.2
Bumps [ldap3](https://github.com/inejge/ldap3) from 0.9.3 to 0.10.2.
- [Release notes](https://github.com/inejge/ldap3/releases)
- [Changelog](https://github.com/inejge/ldap3/blob/master/CHANGELOG.md)
- [Commits](https://github.com/inejge/ldap3/compare/v0.9.3...v0.10.2)

---
updated-dependencies:
- dependency-name: ldap3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-28 19:46:38 +00:00
KitaitiMakoto 8c8c2edc66 Merge pull request 'Add tests for Tag' (#1035) from ap-tests into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1035
2022-02-27 12:50:36 +00:00
Kitaiti Makoto f7e393bded Add tests for Tag 2022-02-27 21:47:40 +09:00
KitaitiMakoto 1df25e34b0 Merge pull request 'Update gettext-macros to 0.6.1' (#1033) from update-gettext-macros into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1033
2022-02-26 15:47:44 +00:00
Kitaiti Makoto ed491bad21 Update gettext-macros to 0.6.1 2022-02-26 11:20:45 +09:00
KitaitiMakoto 306f2d5738 Merge pull request 'Bump bcrypt from 0.10.1 to 0.11.0' (#1032) from bcrypt-0.11.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1032
2022-02-23 20:43:41 +00:00
dependabot[bot] 2196cb95c0 Bump bcrypt from 0.10.1 to 0.11.0
Bumps [bcrypt](https://github.com/Keats/rust-bcrypt) from 0.10.1 to 0.11.0.
- [Release notes](https://github.com/Keats/rust-bcrypt/releases)
- [Commits](https://github.com/Keats/rust-bcrypt/compare/v0.10.1...v0.11.0)

---
updated-dependencies:
- dependency-name: bcrypt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-23 19:27:58 +00:00
KitaitiMakoto b4d494a5c7 Merge pull request 'Bump tracing from 0.1.30 to 0.1.31' (#1030) from tracing-0.1.31 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1030
2022-02-19 18:17:15 +00:00
Kitaiti Makoto e8432f575e Merge remote-tracking branch 'origin/main' into tracing-0.1.31 2022-02-20 02:55:50 +09:00
KitaitiMakoto eb48723c08 Merge pull request 'Bump tracing-subscriber from 0.3.8 to 0.3.9' (#1028) from tracing-subscriber-0.3.9 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1028
2022-02-19 17:28:02 +00:00
dependabot[bot] 65168202b4 Bump tracing from 0.1.30 to 0.1.31
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.30 to 0.1.31.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.30...tracing-0.1.31)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-18 19:38:05 +00:00
dependabot[bot] dced3cf881 Bump tracing-subscriber from 0.3.8 to 0.3.9
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.8 to 0.3.9.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.8...tracing-subscriber-0.3.9)

---
updated-dependencies:
- dependency-name: tracing-subscriber
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-18 19:37:58 +00:00
KitaitiMakoto 170fd6026c Merge pull request 'Bump ammonia from 3.1.3 to 3.1.4' (#1027) from update-crate into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1027
2022-02-18 10:49:25 +00:00
dependabot[bot] 1ccaa817b3 Bump ammonia from 3.1.3 to 3.1.4
Bumps [ammonia](https://github.com/rust-ammonia/ammonia) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/rust-ammonia/ammonia/releases)
- [Changelog](https://github.com/rust-ammonia/ammonia/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-ammonia/ammonia/compare/v3.1.3...v3.1.4)

---
updated-dependencies:
- dependency-name: ammonia
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-17 19:29:15 +00:00
KitaitiMakoto 65ba083720 Merge pull request 'Switch gettext crate from GitHub to crates.io' (#1018) from gettext-cratesio into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1018
2022-02-17 14:32:47 +00:00
Kitaiti Makoto dba902d262 Merge remote-tracking branch 'origin/main' into gettext-cratesio 2022-02-17 23:09:50 +09:00
Kitaiti Makoto d52c7a3afa Update gettext-macros and gettext-utils 2022-02-17 23:08:16 +09:00
KitaitiMakoto c63f88fb7f Merge pull request 'Update crates' (#1026) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1026
2022-02-17 13:58:30 +00:00
Kitaiti Makoto 4412e0598f Follow API change of rocket_i18n 2022-02-17 22:36:13 +09:00
Kitaiti Makoto eb22c1168e Install rocket_i18n from crates.io 2022-02-17 22:36:13 +09:00
Kitaiti Makoto 917eda356d Use rocket_i18n on crates.io 2022-02-17 22:36:13 +09:00
Kitaiti Makoto bc6580bbdc Switch gettext crate from GitHub to crates.io 2022-02-17 22:36:13 +09:00
Kitaiti Makoto 920cf622c5 Merge remote-tracking branch 'github/dependabot/cargo/askama_escape-0.10.3' into update-crates 2022-02-17 22:34:41 +09:00
dependabot[bot] 13dcb193dc Bump askama_escape from 0.10.2 to 0.10.3
Bumps [askama_escape](https://github.com/djc/askama) from 0.10.2 to 0.10.3.
- [Release notes](https://github.com/djc/askama/releases)
- [Commits](https://github.com/djc/askama/commits)

---
updated-dependencies:
- dependency-name: askama_escape
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-16 19:34:15 +00:00
dependabot[bot] 3afb724fed Bump serde_json from 1.0.78 to 1.0.79
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.78 to 1.0.79.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.78...v1.0.79)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-14 19:32:32 +00:00
KitaitiMakoto 9662936b44 Merge pull request 'Update crates' (#1023) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1023
2022-02-08 08:13:37 +00:00
Kitaiti Makoto 4780472d48 Make Circle CI follow ructe change 2022-02-08 16:54:03 +09:00
Kitaiti Makoto 6f68c4504b Update Cargo.lock 2022-02-08 16:53:35 +09:00
Kitaiti Makoto 28e0cdfe63 Remove activitystreams from dependencies 2022-02-08 16:50:00 +09:00
Kitaiti Makoto a5003526c8 Follow clippy 2022-02-08 16:47:48 +09:00
Kitaiti Makoto ec3d78b509 Merge remote-tracking branches 'github/dependabot/cargo/ructe-0.14.0', 'github/dependabot/cargo/rsass-0.23.4' and 'github/dependabot/cargo/tracing-subscriber-0.3.8' into update-crates 2022-02-08 16:37:12 +09:00
dependabot[bot] 4205e38605 Bump rsass from 0.23.2 to 0.23.4
Bumps [rsass](https://github.com/kaj/rsass) from 0.23.2 to 0.23.4.
- [Release notes](https://github.com/kaj/rsass/releases)
- [Changelog](https://github.com/kaj/rsass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/rsass/compare/v0.23.2...v0.23.4)

---
updated-dependencies:
- dependency-name: rsass
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 19:38:02 +00:00
dependabot[bot] 8438d48c71 Bump ructe from 0.13.4 to 0.14.0
Bumps [ructe](https://github.com/kaj/ructe) from 0.13.4 to 0.14.0.
- [Release notes](https://github.com/kaj/ructe/releases)
- [Changelog](https://github.com/kaj/ructe/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/ructe/compare/v0.13.4...v0.14.0)

---
updated-dependencies:
- dependency-name: ructe
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 19:37:50 +00:00
dependabot[bot] 52faf5996b Bump tracing-subscriber from 0.3.7 to 0.3.8
Bumps [tracing-subscriber](https://github.com/tokio-rs/tracing) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-subscriber-0.3.7...tracing-subscriber-0.3.8)

---
updated-dependencies:
- dependency-name: tracing-subscriber
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-07 19:37:34 +00:00
KitaitiMakoto 69eccc50a3 Merge pull request 'Add ActivityPub tests and a little fixes' (#1021) from ap-tests into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1021
2022-02-05 09:17:27 +00:00
Kitaiti Makoto 54cbdb236f Add tests for Mention activity 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 34c374de1a Attach icon field to User activity only whene it has avatar 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 113722e4ba Add more ActivityPub tests for User 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 3b429909f1 Extract User::outbox_collection_page() from outbox_collection() for testablity 2022-02-05 17:58:00 +09:00
Kitaiti Makoto f1cdf4552f Extract User::outbox_collection() from outbox() for testablity 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 7d320e57da Don't make medias::tests::clean() panic when file not found 2022-02-05 17:58:00 +09:00
Kitaiti Makoto e1a598a459 Attach avater to sample user 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 6107842303 Add tests for Comment::to_activity() and build_delete() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 65372d2018 Extract comments::tests::prepare_activity() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 4842385ca6 Add test about reply 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 05f55fc1ca Add https scheme to mention URI in contents 2022-02-05 17:58:00 +09:00
Kitaiti Makoto e8153d4b42 Fix Comment::to_activity() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 2087a659f9 Add test to validate comment json 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 1770336c11 Make format_datetime() crate public 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 9c177f6286 Change format_datetime implementation according to feature 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 93a2c6d99f Add tests for Follow::build_accept() and build_undo() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 5ef76873b7 Fix tests 2022-02-05 17:58:00 +09:00
Kitaiti Makoto b97c3fdb87 Extract Follow::build_accept 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 64838ad864 Add test for Follow::to_activity() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 4df2ce5744 Add mention to test suite for Post activities 2022-02-05 17:58:00 +09:00
Kitaiti Makoto c1f42836d9 Fix variable names 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 0cbc9438d4 Complete a slash to Post Create activity's ID 2022-02-05 17:58:00 +09:00
Kitaiti Makoto ca6cd534d8 Add tests for Post::to_activity(), create_activity() and update_activity() 2022-02-05 17:58:00 +09:00
Kitaiti Makoto f529e803ef Fix ap_url of Reshare 2022-02-05 17:58:00 +09:00
Kitaiti Makoto e5bc84badf Add tests for Reshare::to_activity and build_undo 2022-02-05 17:58:00 +09:00
Kitaiti Makoto e2077bed59 Add test for Like::build_undo 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 9ab9d29efb Remove double slashes 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 5373a674e1 Add test for Like::to_activity 2022-02-05 17:58:00 +09:00
Kitaiti Makoto bfaa2fafaf Install assert-json-diff 2022-02-05 17:58:00 +09:00
Kitaiti Makoto d4a13a13d4 Add assert-json-diff to dev dependencies of plume-common
% cargo add assert-json-diff -p plume-models --dev
2022-02-05 17:58:00 +09:00
Kitaiti Makoto 92c0368dd8 Install activitystreams 0.7.0 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 80c0426768 Add activitystreams 0.7.0 to plume-models dependencies 2022-02-05 17:58:00 +09:00
Kitaiti Makoto 71b21289ab Install activitystreams 0.7.0 2022-02-05 17:58:00 +09:00
Kitaiti Makoto fa861ff314 Add activitystreams 0.7.0 to plume-common dependencies 2022-02-05 17:58:00 +09:00
KitaitiMakoto 400d2dee32 Merge pull request 'Update crates' (#1020) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1020
2022-02-05 05:48:47 +00:00
Kitaiti Makoto 3993dda17d Merge remote-tracking branch 'github/dependabot/cargo/tracing-0.1.30' into update-crates 2022-02-05 14:31:20 +09:00
dependabot[bot] b1255efdcd Bump tracing from 0.1.29 to 0.1.30
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.29 to 0.1.30.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.29...tracing-0.1.30)

---
updated-dependencies:
- dependency-name: tracing
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-04 19:30:06 +00:00
dependabot[bot] 22036c6a94 Bump rsass from 0.23.0 to 0.23.2
Bumps [rsass](https://github.com/kaj/rsass) from 0.23.0 to 0.23.2.
- [Release notes](https://github.com/kaj/rsass/releases)
- [Changelog](https://github.com/kaj/rsass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kaj/rsass/compare/v0.23.0...v0.23.2)

---
updated-dependencies:
- dependency-name: rsass
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-04 19:29:57 +00:00
KitaitiMakoto f2df4b7d7d Merge pull request 'Don't fill empty content when switching rich editor' (#1017) from content-placeholder into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1017
2022-01-29 17:29:41 +00:00
Kitaiti Makoto 7c57bf78a1 [skip ci]Add changelog 2022-01-30 02:29:16 +09:00
Kitaiti Makoto 8d898ff477 Don't fill empty content when switching rich editor 2022-01-30 02:26:23 +09:00
KitaitiMakoto a1045dbce9 Merge pull request 'Fixes #988 Fix email_blocklist schema' (#1016) from block_list-schema into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1016
2022-01-29 16:38:33 +00:00
Kitaiti Makoto 23a07f3f7b [skip ci]Add changelog 2022-01-30 01:17:33 +09:00
Kitaiti Makoto 458d87fef1 Run migration 2022-01-30 01:16:51 +09:00
Kitaiti Makoto 82df86d09e Set null to email_blocklist table fields for SQLite 2022-01-30 01:16:03 +09:00
Kitaiti Makoto 858cad2995 Set null to email_blocklist table fields 2022-01-30 00:54:10 +09:00
Kitaiti Makoto c0483cf12e Generate migration files for adding NOT NULL constraints to email_blocklist table fields
% diesel migration generate add_not_null_constraint_to_email_blocklist
2022-01-30 00:45:18 +09:00
KitaitiMakoto 57a54cf016 Merge pull request 'Update Rust' (#1015) from bump-rust into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1015
2022-01-27 03:14:47 +00:00
Kitaiti Makoto 325d8cde08 [skip ci]Add changelog about Rust bump 2022-01-27 12:01:54 +09:00
Kitaiti Makoto 9e2c76c3bc Satisfy clippy 2022-01-27 11:54:41 +09:00
Kitaiti Makoto 996b161c1e Satisfy clippy 2022-01-27 11:45:35 +09:00
Kitaiti Makoto 831ef88431 Update Rust 2022-01-27 11:45:21 +09:00
KitaitiMakoto 89517e5988 Merge pull request 'Update crates' (#1014) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1014
2022-01-27 01:22:16 +00:00
Kitaiti Makoto 48dbcf75a9 Update crates 2022-01-27 10:02:47 +09:00
Kitaiti Makoto a56a9bc9c5 Add changelogs 2022-01-27 10:02:30 +09:00
KitaitiMakoto 918103fa29 Merge pull request 'Fix #1011 Add Basque' (#1013) from langs into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1013
2022-01-26 13:37:53 +00:00
KitaitiMakoto c9b8f5a739 Merge pull request 'Fix #1009 Email Sign-up Explanation' (#1012) from email-signup-explanation into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1012
2022-01-26 13:37:38 +00:00
Kitaiti Makoto d58ff36d80 Update po files 2022-01-26 22:17:11 +09:00
Kitaiti Makoto 00d647c0ad Add Basque po files 2022-01-26 22:16:55 +09:00
Kitaiti Makoto a27f196578 Add Basque 2022-01-26 22:15:35 +09:00
Kitaiti Makoto abe82b79ce Update pot file 2022-01-26 22:10:47 +09:00
Kitaiti Makoto 95230c3a23 Add explanation for email signup 2022-01-26 22:10:14 +09:00
Kitaiti Makoto eade69a12c Fix indentation 2022-01-26 22:09:45 +09:00
KitaitiMakoto 4f89e214ef Merge pull request 'Update crates' (#1010) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1010
2022-01-25 00:46:41 +00:00
Kitaiti Makoto 2936679326 Update crates 2022-01-25 09:26:23 +09:00
KitaitiMakoto 18a67fe1b5 Merge pull request 'Update crates' (#1008) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1008
2022-01-16 03:23:13 +00:00
Kitaiti Makoto ba29c8ef6f Follow atom_syndication API change 2022-01-16 12:02:12 +09:00
Kitaiti Makoto d253f1a020 Upgrade atom_syndication 2022-01-16 12:02:03 +09:00
Kitaiti Makoto 03060d6ee2 Update crates 2022-01-16 11:31:18 +09:00
KitaitiMakoto ac8ad3aae2 Merge pull request 'Add dependabot.yml' (#1007) from dependabot into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1007
2022-01-15 02:01:40 +00:00
Kitaiti Makoto 14e294efed Add dependabot.yml 2022-01-15 06:02:16 +09:00
KitaitiMakoto ec3205b372 Merge pull request 'v0.7.1' (#1006) from v0.7.1 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1006
2022-01-12 02:10:48 +00:00
Kitaiti Makoto 45119d9a8c (cargo-release) version {{version}} 2022-01-12 10:42:45 +09:00
Kitaiti Makoto 1065078f75 Update translation files 2022-01-12 10:29:50 +09:00
Kitaiti Makoto 0ce904a985 Update translation files 2022-01-12 10:24:14 +09:00
Kitaiti Makoto 254eec8e6a Follow cargo-release update 2022-01-12 10:19:58 +09:00
KitaitiMakoto 0e4cb4f6e1 Merge pull request '[skip ci]Fix # of pull reuqest in changelog' (#1004) from fix-changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1004
2022-01-12 00:37:19 +00:00
Kitaiti Makoto 9b05ac90df [skip ci]Fix # of pull reuqest in changelog 2022-01-12 09:36:32 +09:00
KitaitiMakoto f28a7fa508 Merge pull request 'Add changelogs' (#1003) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1003
2022-01-12 00:35:26 +00:00
Kitaiti Makoto 65e95d8998 Add changelogs 2022-01-12 09:34:39 +09:00
KitaitiMakoto 808b8f8e98 Merge pull request 'Fix #1001 Deny access to disabled sign-up strategy' (#1002) from restrict-signup into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/1002
2022-01-12 00:33:04 +00:00
Kitaiti Makoto 43b46a8be4 Make email_signups::create return ErrorPage on error 2022-01-12 09:20:20 +09:00
Kitaiti Makoto 9bbfc71fc8 Fix registration openess condition mistake 2022-01-12 09:19:48 +09:00
Kitaiti Makoto 5d58b31f1c Remove unreachable code 2022-01-12 09:00:04 +09:00
Kitaiti Makoto e31a2238fb Respond with error status code when error 2022-01-12 08:58:42 +09:00
Kitaiti Makoto 7de37bc9b7 Hide password sign-up routings when it's disabled 2022-01-12 08:46:11 +09:00
Kitaiti Makoto 13f7734751 Hide email sign-up routings when it's disabled 2022-01-12 08:40:20 +09:00
Kitaiti Makoto b4395bce99 Implement request guard to detect enabled sign-up strategy 2022-01-12 08:39:33 +09:00
Kitaiti Makoto 7c82b08615 Use into() instead of explicitly wrapping return values 2022-01-12 04:36:01 +09:00
Kitaiti Makoto 6498dbfbb7 Reuse form values 2022-01-12 04:28:02 +09:00
Kitaiti Makoto 74254aed4a Move require_logins from plume-common to plume 2022-01-12 04:18:13 +09:00
KitaitiMakoto 8c48abf48e Merge pull request 'Update crates' (#997) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/997
2022-01-07 18:58:18 +00:00
Kitaiti Makoto 8958226604 Upgrade shrinkwraprs 2022-01-08 03:54:38 +09:00
Kitaiti Makoto 005a6db230 Update crates 2022-01-08 03:20:22 +09:00
KitaitiMakoto 4397abd8ab Merge pull request 'Add plume-front.pot' (#994) from front-po into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/994
2022-01-07 15:40:31 +00:00
Kitaiti Makoto e53882f555 Add plume-front.pot 2022-01-08 00:39:17 +09:00
KitaitiMakoto 5d5e61dfa1 Merge pull request 'Update crates' (#993) from update-crates into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/993
2022-01-06 21:45:53 +00:00
Kitaiti Makoto c5c6b70a89 Upgrade ldap3 2022-01-07 06:17:58 +09:00
Kitaiti Makoto 6778a0e943 Remove hyper from plume-common 2022-01-07 06:16:00 +09:00
Kitaiti Makoto 677e238c6d Follow API change of heck 2022-01-07 06:12:15 +09:00
Kitaiti Makoto b0bc2372fa Upgrade heck 2022-01-07 06:12:03 +09:00
Kitaiti Makoto 6a808c7cc5 Upgrade hex 2022-01-07 06:07:07 +09:00
Kitaiti Makoto d53543ccb1 Upgrade base64 2022-01-07 06:06:49 +09:00
Kitaiti Makoto 88d7d54601 Upgrade whatlang 2022-01-07 06:05:20 +09:00
Kitaiti Makoto 0f0c896887 Upgrade itertools 2022-01-07 06:05:05 +09:00
Kitaiti Makoto 65233c0a9a Upgrade ammonia 2022-01-07 05:53:09 +09:00
Kitaiti Makoto 32e1e4788f Upgrade shrinkwraprs 2022-01-07 05:49:06 +09:00
Kitaiti Makoto 181a78876b Remove askama_escape from dependencies of plume-models 2022-01-07 05:47:46 +09:00
Kitaiti Makoto 61d5446113 Use plume_common::escape() instead of askama_escape::escape() directly 2022-01-07 05:47:08 +09:00
Kitaiti Makoto c786569171 Define plume_common::escape() 2022-01-07 05:36:39 +09:00
Kitaiti Makoto d83a75e3f4 Add askama_escape to plume-common 2022-01-07 05:26:32 +09:00
Kitaiti Makoto a6f06559ea Remove rspassword from dependencies 2022-01-07 05:15:07 +09:00
Kitaiti Makoto 2084145dd3 Upgrade multipart 2022-01-07 05:12:29 +09:00
Kitaiti Makoto dd54058516 Upgrade guid-create 2022-01-07 05:09:08 +09:00
Kitaiti Makoto 4056a54d44 Upgrade tracing-subscriber 2022-01-07 05:03:07 +09:00
Kitaiti Makoto 191cd11741 Upgrade dotenv 2022-01-07 04:59:03 +09:00
Kitaiti Makoto 800e74da67 Follow API change of validator 2022-01-07 04:55:49 +09:00
Kitaiti Makoto 237da47950 Upgrade validator 2022-01-07 04:55:41 +09:00
Kitaiti Makoto ec12539fd0 Follow rsass API change 2022-01-07 04:41:20 +09:00
Kitaiti Makoto a537db559b Upgrade rsass 2022-01-07 04:41:11 +09:00
Kitaiti Makoto 2ba158df67 Update crates 2022-01-07 01:41:22 +09:00
KitaitiMakoto c0c066547f Merge pull request 'Update po files' (#991) from po into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/991
2022-01-06 13:24:09 +00:00
Kitaiti Makoto c3f59b14b9 Update po files 2022-01-06 22:14:24 +09:00
KitaitiMakoto 1d06a8f1ad Merge pull request 'Fixes #636 Email sign up feature' (#990) from mail-confirmation into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/990
2022-01-06 12:04:24 +00:00
Kitaiti Makoto efaf1295e9 Suppress clippy 2022-01-06 20:43:40 +09:00
Kitaiti Makoto 4bc9cf3ad1 [skip ci]Complete changelogs 2022-01-06 20:35:34 +09:00
Kitaiti Makoto 1e3851ea69 Execute SQLs for email_signups in transaction 2022-01-06 20:27:55 +09:00
Kitaiti Makoto b6d38536e3 Add email signup feature 2022-01-06 20:18:20 +09:00
Kitaiti Makoto 9b4c678aa9 Make signup token transparent 2022-01-06 18:40:24 +09:00
Kitaiti Makoto a65775d85b Implement EmailSignup 2022-01-05 03:17:58 +09:00
Kitaiti Makoto 192c7677c3 Run migration
% diesel migration run
2022-01-05 00:01:36 +09:00
Kitaiti Makoto 2a31a7b601 Define email_singups table 2022-01-05 00:01:36 +09:00
Kitaiti Makoto 355fd7cb1d Generate create_email_signups_table migration
% diesel migration generate create_email_signups_table
2022-01-05 00:01:36 +09:00
Kitaiti Makoto 40efd73dfc Add config for sign up strategy 2022-01-05 00:01:32 +09:00
KitaitiMakoto 31b144c76d Merge pull request 'Remove unnecessary prefix' (#986) from fix-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/986
2022-01-04 10:17:53 +00:00
Kitaiti Makoto 31a46514cb Remove unnecessary prefix 2022-01-04 19:17:04 +09:00
KitaitiMakoto c8d906eb99 Merge pull request 'Quote version tag' (#985) from fix-action into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/985
2022-01-04 10:11:19 +00:00
Kitaiti Makoto 2895a1c819 Quote version tag 2022-01-04 19:10:37 +09:00
KitaitiMakoto a4a5d08662 Merge pull request 'deploy-tags' (#984) from deploy-tags into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/984
2022-01-04 10:08:31 +00:00
Kitaiti Makoto b97c9d2165 Deploy Docker images with tags 2022-01-04 19:07:47 +09:00
Kitaiti Makoto 5b7e8a69a5 Revert "Deploy tags"
This reverts commit d9a59f1b07.
2022-01-04 19:04:58 +09:00
KitaitiMakoto 9601e99e33 Merge pull request 'Deploy tags' (#983) from deploy-tags into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/983
2022-01-04 09:42:19 +00:00
Kitaiti Makoto d9a59f1b07 Deploy tags 2022-01-04 18:41:06 +09:00
KitaitiMakoto b6a6af906a Merge pull request '[skip ci]Add changelog abourt MAIL_PORT' (#982) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/982
2022-01-03 16:07:13 +00:00
Kitaiti Makoto 2c4799ce27 [skip ci]Add changelog abourt MAIL_PORT 2022-01-04 01:06:12 +09:00
KitaitiMakoto b33b19849c Merge pull request 'Fix notification page error' (#981) from fix-notification-page into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/981
2022-01-03 14:29:43 +00:00
Kitaiti Makoto e398f36c57 Add changelog 2022-01-03 23:28:56 +09:00
Kitaiti Makoto ee6064eee8 Don't unwrap() 2022-01-03 23:28:05 +09:00
KitaitiMakoto 9d012c8f3c Merge pull request 'Closes #944 Mail server port configuration' (#980) from mail-server-port into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/980
2022-01-03 10:37:53 +00:00
Kitaiti Makoto 8888dbba0a Initialize SMTP client with port number 2022-01-03 18:09:26 +09:00
Kitaiti Makoto 6f8d5c1eb4 Add SmtpClient::new_with_addr() method 2022-01-03 18:09:13 +09:00
Kitaiti Makoto 62da4a3d5c Add native-tls to plume-models' dependencies 2022-01-03 18:04:49 +09:00
Kitaiti Makoto 5cfc8e71a5 Remove Lettre from plume module dependencies 2022-01-03 17:36:37 +09:00
Kitaiti Makoto a599760891 Use smtp module from plume_models instead of lettre directly 2022-01-03 17:36:11 +09:00
Kitaiti Makoto 00324f668f Add port field to MailConfig 2022-01-03 17:25:46 +09:00
Kitaiti Makoto d4549704b9 Install Lettre 2022-01-03 17:11:35 +09:00
Kitaiti Makoto 0836e3d693 Add Lettre to plume-models' dependencies 2022-01-03 17:09:47 +09:00
Kitaiti Makoto 0058c3053d Move mail config from plume::mail::mailer to plume_models::CONFIG 2022-01-03 15:50:04 +09:00
KitaitiMakoto 2a1a0a23a5 Merge pull request 'Move bottombar styles to _article.scss' (#978) from bottombar-styling into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/978
2022-01-02 18:03:46 +00:00
Kitaiti Makoto 5614e3bd59 Move bottombar styles to _article.scss 2022-01-03 03:01:59 +09:00
KitaitiMakoto acbda3cde1 Merge pull request 'Make bottom bar smaller in narrow window' (#977) from layout-post-control into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/977
2022-01-02 17:50:20 +00:00
Kitaiti Makoto 0755436458 Make bottom bar smaller in narrow window 2022-01-03 02:49:45 +09:00
KitaitiMakoto 3daf405ae2 Merge pull request 'Make blog cover clickable' (#976) from clickable-blog-image into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/976
2022-01-02 17:40:30 +00:00
Kitaiti Makoto 53dc3b0c03 Fix cover size of posts 2022-01-03 02:39:17 +09:00
Kitaiti Makoto 371dcc5091 Address blog title positoin in dashboard 2022-01-03 02:37:45 +09:00
Kitaiti Makoto 905fe54fa3 Make blog cover a link 2022-01-03 02:35:07 +09:00
Kitaiti Makoto 62c0827ff5 Remove needless whitespaces 2022-01-03 02:32:17 +09:00
KitaitiMakoto d6c65ce81a Merge pull request 'Fix #927 Ensure Post ap_url' (#975) from ensure-ap-url into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/975
2022-01-02 17:24:04 +00:00
Kitaiti Makoto 5532b4a4d7 Ensure Post ap_url 2022-01-03 02:21:34 +09:00
KitaitiMakoto 637bd3347b Merge pull request 'Fix #967 Fix comment link' (#974) from fix-comment-link into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/974
2022-01-02 17:08:10 +00:00
Kitaiti Makoto bac373a818 Fix comment link 2022-01-03 02:06:59 +09:00
Kitaiti Makoto f0e7ea5640 (cargo-release) version {{version}} 2022-01-03 01:17:07 +09:00
Kitaiti Makoto 4b981e0fad (cargo-release) version {{version}} 2022-01-03 00:56:29 +09:00
KitaitiMakoto 8fb9d861de Merge pull request '[skip ci]Give up to deploy tags' (#973) from giveup-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/973
2022-01-02 15:43:05 +00:00
Kitaiti Makoto 199269ba3c [skip ci]Give up to deploy tags 2022-01-03 00:42:36 +09:00
KitaitiMakoto 0b9ec4c52c Merge pull request 'Use Git tag for Docker image tag' (#972) from deploy-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/972
2022-01-02 15:14:38 +00:00
Kitaiti Makoto 0e51565cc8 Use Git tag for Docker image tag 2022-01-03 00:14:00 +09:00
KitaitiMakoto 0418d35b67 Merge pull request 'Fix a typo' (#971) from deploy-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/971
2022-01-02 15:06:26 +00:00
Kitaiti Makoto bf9d25363b Fix a typo 2022-01-03 00:04:57 +09:00
KitaitiMakoto 84f00c57d1 Merge pull request 'Fix tag calculation' (#970) from deploy-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/970
2022-01-02 15:02:15 +00:00
Kitaiti Makoto 7c1a5421fa Fix tag calculation 2022-01-03 00:01:18 +09:00
KitaitiMakoto 3815bfe980 Merge pull request '[skip ci]Deploy tags to Docker Hub' (#969) from deploy-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/969
2022-01-02 14:51:03 +00:00
Kitaiti Makoto de448c3192 [skip ci]Deploy tags to Docker Hub 2022-01-02 23:49:42 +09:00
KitaitiMakoto 967e2dfde6 Merge pull request 'Add GitHub Action to deploy Docker image' (#968) from gh-action into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/968
2022-01-02 14:28:27 +00:00
Kitaiti Makoto dd3c1eac5f Add GitHub Action to deploy Docker image 2022-01-02 23:26:39 +09:00
KitaitiMakoto abc0a794c1 Merge pull request 'v0.7.0' (#923) from v0.7.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/923
2022-01-02 11:48:46 +00:00
165 changed files with 10997 additions and 6319 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ executors:
default: false default: false
docker: docker:
- image: plumeorg/plume-buildenv:v0.4.0 - image: plumeorg/plume-buildenv:v0.4.0
- image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>> - image: <<#parameters.postgres>>cimg/postgres:14.2<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>>
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_DB: plume POSTGRES_DB: plume
@@ -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
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: cargo
directory: /
schedule:
interval: daily
@@ -0,0 +1,30 @@
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
+36
View File
@@ -0,0 +1,36 @@
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 }}
+47 -1
View File
@@ -4,6 +4,50 @@
## [Unreleased] - ReleaseDate ## [Unreleased] - ReleaseDate
## [[0.7.2]] - 2022-05-11
### Added
- Basque language (#1013)
- Unit tests for ActivityPub (#1021)
- Move to action area after liking/boosting/commenting (#1074)
### Changed
- Bump Rust to nightly 2022-01-26 (#1015)
- Remove "Latest articles" timeline (#1069)
- Change order of timeline tabs (#1069, #1070, #1072)
- Migrate ActivityPub-related crates from activitypub 0.1 to activitystreams 0.7 (#1022)
### Fixed
- Add explanation of sign-up step at sign-up page when email sign-up mode (#1012)
- Add NOT NULL constraint to email_blocklist table fields (#1016)
- Don't fill empty content when switching rich editor (#1017)
- Fix accept header (#1058)
- Render 404 page instead of 500 when data is not found (#1062)
- Reuse reqwest client on broadcasting (#1059)
- Reduce broadcasting HTTP request at once to prevent them being timed out (#1068, #1071)
- Some ActivityPub data (#1021)
## [[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 ## [[0.7.0]] - 2022-01-02
### Added ### Added
@@ -218,7 +262,9 @@
- Ability to create multiple blogs - Ability to create multiple blogs
<!-- next-url --> <!-- next-url -->
[Unreleased]: https://github.com/Plume-org/Plume/compare/0.7.0...HEAD [Unreleased]: https://github.com/Plume-org/Plume/compare/0.7.2...HEAD
[[0.7.2]]: https://github.com/Plume-org/Plume/compare/0.7.1...0.7.2
[[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.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
Generated
+1187 -951
View File
File diff suppressed because it is too large Load Diff
+17 -21
View File
@@ -1,37 +1,33 @@
[package] [package]
authors = ["Plume contributors"] authors = ["Plume contributors"]
name = "plume" name = "plume"
version = "0.7.0" version = "0.7.2"
repository = "https://github.com/Plume-org/Plume" repository = "https://github.com/Plume-org/Plume"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
activitypub = "0.1.3" atom_syndication = "0.11.0"
askama_escape = "0.1"
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 = "0.4.0"
gettext-macros = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-macros = "0.6.1"
gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-utils = "0.1.0"
guid-create = "0.1" guid-create = "0.2"
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 = "0.4.1"
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.81"
shrinkwraprs = "0.2.1" shrinkwraprs = "0.3.0"
validator = "0.8" validator = { version = "0.15", features = ["derive"] }
validator_derive = "0.8"
webfinger = "0.4.1" webfinger = "0.4.1"
tracing = "0.1.22" tracing = "0.1.34"
tracing-subscriber = "0.2.15" tracing-subscriber = "0.3.10"
riker = "0.4.2" riker = "0.4.2"
activitystreams = "0.7.0-alpha.18"
[[bin]] [[bin]]
name = "plume" name = "plume"
@@ -43,7 +39,7 @@ version = "0.4"
[dependencies.ctrlc] [dependencies.ctrlc]
features = ["termination"] features = ["termination"]
version = "3.1.2" version = "3.2.2"
[dependencies.diesel] [dependencies.diesel]
features = ["r2d2", "chrono"] features = ["r2d2", "chrono"]
@@ -52,7 +48,7 @@ version = "1.4.5"
[dependencies.multipart] [dependencies.multipart]
default-features = false default-features = false
features = ["server"] features = ["server"]
version = "0.16" version = "0.18"
[dependencies.plume-api] [dependencies.plume-api]
path = "plume-api" path = "plume-api"
@@ -68,8 +64,8 @@ git = "https://github.com/fdb-hiroshima/rocket_csrf"
rev = "29910f2829e7e590a540da3804336577b48c7b31" rev = "29910f2829e7e590a540da3804336577b48c7b31"
[build-dependencies] [build-dependencies]
ructe = "0.13.0" ructe = "0.14.0"
rsass = "0.9" rsass = "0.24"
[features] [features]
default = ["postgres"] default = ["postgres"]
+1
View File
@@ -53,3 +53,4 @@ As we want the various spaces related to the project (GitHub, Matrix, Loomio, et
We provide various way to install Plume: from source, with pre-built binaries, with Docker or with YunoHost. We provide various way to install Plume: from source, with pre-built binaries, with Docker or with YunoHost.
For detailed explanations, please refer to [the documentation](https://docs.joinplu.me/installation/). For detailed explanations, please refer to [the documentation](https://docs.joinplu.me/installation/).
+7
View File
@@ -516,4 +516,11 @@ 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;
}
}
} }
+14 -14
View File
@@ -219,17 +219,21 @@ 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;
&:hover {
opacity: 0.9;
}
} }
header { header {
display: flex; display: flex;
} }
@@ -245,6 +249,9 @@ p.error {
position: relative; 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;
@@ -500,6 +507,7 @@ figure {
margin: auto $horizontal-margin 2em; margin: auto $horizontal-margin 2em;
overflow: auto; overflow: auto;
display: flex; display: flex;
justify-content: center;
a { a {
display: inline-block; display: inline-block;
@@ -569,14 +577,6 @@ 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;
@@ -611,4 +611,4 @@ code {
.function{ .function{
color:inherit; color:inherit;
} }
} }
+8 -2
View File
@@ -120,8 +120,14 @@ 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_file(path, rsass::OutputStyle::Compressed) &rsass::compile_scss_path(
.expect("SCSS compilation error"), path,
rsass::output::Format {
style: rsass::output::Style::Compressed,
..rsass::output::Format::default()
},
)
.expect("SCSS compilation error"),
)?; )?;
Ok(()) Ok(())
@@ -0,0 +1 @@
DROP TABLE email_signups;
@@ -0,0 +1,9 @@
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,4 @@
ALTER TABLE email_blocklist ALTER COLUMN notification_text DROP NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN notify_user DROP NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN note DROP NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN email_address DROP NOT NULL;
@@ -0,0 +1,4 @@
ALTER TABLE email_blocklist ALTER COLUMN email_address SET NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN note SET NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN notify_user SET NOT NULL;
ALTER TABLE email_blocklist ALTER COLUMN notification_text SET NOT NULL;
@@ -0,0 +1 @@
DROP TABLE email_signups;
@@ -0,0 +1,9 @@
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);
@@ -0,0 +1,9 @@
CREATE TABLE email_blocklist2(id INTEGER PRIMARY KEY,
email_address TEXT UNIQUE,
note TEXT,
notify_user BOOLEAN DEFAULT FALSE,
notification_text TEXT);
INSERT INTO email_blocklist2 SELECT * FROM email_blocklist;
DROP TABLE email_blocklist;
ALTER TABLE email_blocklist2 RENAME TO email_blocklist;
@@ -0,0 +1,9 @@
CREATE TABLE email_blocklist2(id INTEGER PRIMARY KEY,
email_address TEXT UNIQUE NOT NULL,
note TEXT NOT NULL,
notify_user BOOLEAN DEFAULT FALSE NOT NULL,
notification_text TEXT NOT NULL);
INSERT INTO email_blocklist2 SELECT * FROM email_blocklist;
DROP TABLE email_blocklist;
ALTER TABLE email_blocklist2 RENAME TO email_blocklist;
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-api" name = "plume-api"
version = "0.7.0" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-cli" name = "plume-cli"
version = "0.7.0" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
@@ -10,8 +10,8 @@ path = "src/main.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
dotenv = "0.14" dotenv = "0.15"
rpassword = "5.0.0" rpassword = "6.0.1"
[dependencies.diesel] [dependencies.diesel]
features = ["r2d2", "chrono"] features = ["r2d2", "chrono"]
+19 -14
View File
@@ -1,29 +1,31 @@
[package] [package]
name = "plume-common" name = "plume-common"
version = "0.7.0" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
activitypub = "0.1.1"
activitystreams-derive = "0.1.1"
activitystreams-traits = "0.1.0"
array_tool = "1.0" array_tool = "1.0"
base64 = "0.10" base64 = "0.13"
heck = "0.3.0" heck = "0.4.0"
hex = "0.3" hex = "0.4"
hyper = "0.12.33" openssl = "0.10.40"
openssl = "0.10.22"
rocket = "0.4.6" rocket = "0.4.6"
reqwest = { version = "0.9", features = ["socks"] } reqwest = { version = "0.11.10", features = ["blocking", "json", "socks"] }
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0.70" serde_json = "1.0.81"
shrinkwraprs = "0.3.0" shrinkwraprs = "0.3.0"
syntect = "4.5.0" syntect = "4.5.0"
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.34"
askama_escape = "0.10.3"
activitystreams = "0.7.0-alpha.18"
activitystreams-ext = "0.1.0-alpha.2"
url = "2.2.2"
flume = "0.10.12"
tokio = { version = "1.18.2", features = ["full"] }
futures = "0.3.21"
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
@@ -35,4 +37,7 @@ git = "https://git.joinplu.me/Plume/pulldown-cmark"
branch = "bidi-plume" branch = "bidi-plume"
[dev-dependencies] [dev-dependencies]
once_cell = "1.5.2" assert-json-diff = "2.0.1"
once_cell = "1.10.0"
[features]
+59 -56
View File
@@ -10,8 +10,7 @@ use super::{request, sign::Signer};
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # extern crate activitypub; /// # use activitystreams::{prelude::*, base::Base, actor::Person, activity::{Announce, Create}, object::Note, iri_string::types::IriString};
/// # 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 once_cell::sync::Lazy;
/// # use plume_common::activity_pub::inbox::*; /// # use plume_common::activity_pub::inbox::*;
@@ -113,12 +112,13 @@ use super::{request, sign::Signer};
/// # } /// # }
/// # } /// # }
/// # /// #
/// # let mut act = Create::default(); /// # let mut person = Person::new();
/// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap(); /// # person.set_id("https://test.ap/actor".parse::<IriString>().unwrap());
/// # let mut person = Person::default(); /// # let mut act = Create::new(
/// # person.object_props.set_id_string(String::from("https://test.ap/actor")).unwrap(); /// # Base::retract(person).unwrap().into_generic().unwrap(),
/// # act.create_props.set_actor_object(person).unwrap(); /// # Base::retract(Note::new()).unwrap().into_generic().unwrap()
/// # act.create_props.set_object_object(Note::default()).unwrap(); /// # );
/// # act.set_id("https://test.ap/activity".parse::<IriString>().unwrap());
/// # let activity_json = serde_json::to_value(act).unwrap(); /// # let activity_json = serde_json::to_value(act).unwrap();
/// # /// #
/// # let conn = (); /// # let conn = ();
@@ -197,29 +197,29 @@ where
} }
/// Registers an handler on this Inbox. /// Registers an handler on this Inbox.
pub fn with<A, V, M>(self, proxy: Option<&reqwest::Proxy>) -> Inbox<'a, C, E, R> pub fn with<A, V, M>(self, proxy: Option<&reqwest::Proxy>) -> Self
where where
A: AsActor<&'a C> + FromId<C, Error = E>, A: AsActor<&'a C> + FromId<C, Error = E>,
V: activitypub::Activity, V: activitystreams::markers::Activity + serde::de::DeserializeOwned,
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 Self::NotHandled(ctx, 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 Self::NotHandled(ctx, 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 Self::NotHandled(ctx, 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 Self::NotHandled(ctx, 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)
@@ -235,14 +235,14 @@ 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 Self::NotHandled(ctx, 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 Self::NotHandled(ctx, act, InboxError::InvalidObject(None)),
}; };
let obj = match M::from_id( let obj = match M::from_id(
ctx, ctx,
@@ -255,19 +255,19 @@ 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 Self::NotHandled(ctx, 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) => Self::Handled(res.into()),
Err(e) => Inbox::Failed(e), Err(e) => Self::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) Self::NotHandled(ctx, act, e)
} }
} else { } else {
self self
@@ -333,7 +333,7 @@ pub trait FromId<C>: Sized {
type Error: From<InboxError<Self::Error>> + Debug; type Error: From<InboxError<Self::Error>> + Debug;
/// The ActivityPub object type representing Self /// The ActivityPub object type representing Self
type Object: activitypub::Object; type Object: activitystreams::markers::Object + serde::de::DeserializeOwned;
/// Tries to get an instance of `Self` from an ActivityPub ID. /// Tries to get an instance of `Self` from an ActivityPub ID.
/// ///
@@ -366,7 +366,7 @@ pub trait FromId<C>: Sized {
) -> 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) request::get(id, Self::get_sender(), proxy)
.map_err(|_| (None, InboxError::DerefError)) .map_err(|_| (None, InboxError::DerefError))
.and_then(|mut r| { .and_then(|r| {
let json: serde_json::Value = r let json: serde_json::Value = r
.json() .json()
.map_err(|_| (None, InboxError::InvalidObject(None)))?; .map_err(|_| (None, InboxError::InvalidObject(None)))?;
@@ -418,8 +418,7 @@ pub trait AsActor<C> {
/// representing the Note by a Message type, without any specific context. /// representing the Note by a Message type, without any specific context.
/// ///
/// ```rust /// ```rust
/// # extern crate activitypub; /// # use activitystreams::{prelude::*, 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 plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; /// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
@@ -501,7 +500,10 @@ 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.content()
/// .and_then(|content| content.to_owned().single_xsd_string()).ok_or(())?
/// })
/// } /// }
/// ///
/// fn get_sender() -> &'static dyn Signer { /// fn get_sender() -> &'static dyn Signer {
@@ -521,7 +523,7 @@ pub trait AsActor<C> {
/// ``` /// ```
pub trait AsObject<A, V, C> pub trait AsObject<A, V, C>
where where
V: activitypub::Activity, V: activitystreams::markers::Activity,
{ {
/// What kind of error is returned when something fails /// What kind of error is returned when something fails
type Error; type Error;
@@ -549,7 +551,13 @@ mod tests {
use crate::activity_pub::sign::{ use crate::activity_pub::sign::{
gen_keypair, Error as SignError, Result as SignResult, Signer, gen_keypair, Error as SignError, Result as SignResult, Signer,
}; };
use activitypub::{activity::*, actor::Person, object::Note}; use activitystreams::{
activity::{Announce, Create, Delete, Like},
actor::Person,
base::Base,
object::Note,
prelude::*,
};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
@@ -598,11 +606,11 @@ mod tests {
type Object = Person; type Object = Person;
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> { fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
Ok(MyActor) Ok(Self)
} }
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> { fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
Ok(MyActor) Ok(Self)
} }
fn get_sender() -> &'static dyn Signer { fn get_sender() -> &'static dyn Signer {
@@ -626,11 +634,11 @@ mod tests {
type Object = Note; type Object = Note;
fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> { fn from_db(_: &(), _id: &str) -> Result<Self, Self::Error> {
Ok(MyObject) Ok(Self)
} }
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> { fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
Ok(MyObject) Ok(Self)
} }
fn get_sender() -> &'static dyn Signer { fn get_sender() -> &'static dyn Signer {
@@ -678,21 +686,15 @@ mod tests {
} }
fn build_create() -> Create { fn build_create() -> Create {
let mut act = Create::default(); let mut person = Person::new();
act.object_props person.set_id("https://test.ap/actor".parse().unwrap());
.set_id_string(String::from("https://test.ap/activity")) let mut note = Note::new();
.unwrap(); note.set_id("https://test.ap/note".parse().unwrap());
let mut person = Person::default(); let mut act = Create::new(
person Base::retract(person).unwrap().into_generic().unwrap(),
.object_props Base::retract(note).unwrap().into_generic().unwrap(),
.set_id_string(String::from("https://test.ap/actor")) );
.unwrap(); act.set_id("https://test.ap/activity".parse().unwrap());
act.create_props.set_actor_object(person).unwrap();
let mut note = Note::default();
note.object_props
.set_id_string(String::from("https://test.ap/note"))
.unwrap();
act.create_props.set_object_object(note).unwrap();
act act
} }
@@ -729,6 +731,16 @@ mod tests {
} }
struct FailingActor; struct FailingActor;
impl AsActor<&()> for FailingActor {
fn get_inbox_url(&self) -> String {
String::from("https://test.ap/failing-actor/inbox")
}
fn is_local(&self) -> bool {
false
}
}
impl FromId<()> for FailingActor { impl FromId<()> for FailingActor {
type Error = (); type Error = ();
type Object = Person; type Object = Person;
@@ -737,7 +749,7 @@ mod tests {
Err(()) Err(())
} }
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> { fn from_activity(_: &(), _obj: Self::Object) -> Result<Self, Self::Error> {
Err(()) Err(())
} }
@@ -745,15 +757,6 @@ mod tests {
&*MY_SIGNER &*MY_SIGNER
} }
} }
impl AsActor<&()> for FailingActor {
fn get_inbox_url(&self) -> String {
String::from("https://test.ap/failing-actor/inbox")
}
fn is_local(&self) -> bool {
false
}
}
impl AsObject<FailingActor, Create, &()> for MyObject { impl AsObject<FailingActor, Create, &()> for MyObject {
type Error = (); type Error = ();
+563 -92
View File
@@ -1,13 +1,27 @@
use activitypub::{Activity, Link, Object}; use activitystreams::{
actor::{ApActor, Group, Person},
base::{AnyBase, Base, Extends},
iri_string::types::IriString,
kind,
markers::{self, Activity},
object::{ApObject, Article, Object},
primitives::{AnyString, OneOrMany},
unparsed::UnparsedMutExt,
};
use activitystreams_ext::{Ext1, Ext2, UnparsedExtension};
use array_tool::vec::Uniq; use array_tool::vec::Uniq;
use reqwest::{header::HeaderValue, r#async::ClientBuilder, Url}; use futures::future::join_all;
use reqwest::{header::HeaderValue, ClientBuilder, RequestBuilder, Url};
use rocket::{ use rocket::{
http::Status, http::Status,
request::{FromRequest, Request}, request::{FromRequest, Request},
response::{Responder, Response}, response::{Responder, Response},
Outcome, Outcome,
}; };
use tokio::prelude::*; use tokio::{
runtime,
time::{sleep, Duration},
};
use tracing::{debug, warn}; use tracing::{debug, warn};
use self::sign::Signable; use self::sign::Signable;
@@ -24,8 +38,8 @@ pub const AP_CONTENT_TYPE: &str =
pub fn ap_accept_header() -> Vec<&'static str> { pub fn ap_accept_header() -> Vec<&'static str> {
vec![ vec![
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\"", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"application/ld+json;profile=\"https://w3.org/ns/activitystreams\"", "application/ld+json;profile=\"https://www.w3.org/ns/activitystreams\"",
"application/activity+json", "application/activity+json",
"application/ld+json", "application/ld+json",
] ]
@@ -63,7 +77,7 @@ impl<T> ActivityStream<T> {
} }
} }
impl<'r, O: Object> Responder<'r> for ActivityStream<O> { impl<'r, O: serde::Serialize> Responder<'r> for ActivityStream<O> {
fn respond_to(self, request: &Request<'_>) -> Result<Response<'r>, Status> { fn respond_to(self, request: &Request<'_>) -> Result<Response<'r>, Status> {
let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?; let mut json = serde_json::to_value(&self.0).map_err(|_| Status::InternalServerError)?;
json["@context"] = context(); json["@context"] = context();
@@ -87,14 +101,16 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApRequest {
.map(|header| { .map(|header| {
header header
.split(',') .split(',')
.map(|ct| match ct.trim() { .map(|ct| {
match ct.trim() {
// bool for Forward: true if found a valid Content-Type for Plume first (HTML), false otherwise // bool for Forward: true if found a valid Content-Type for Plume first (HTML), false otherwise
"application/ld+json; profile=\"https://w3.org/ns/activitystreams\"" "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
| "application/ld+json;profile=\"https://w3.org/ns/activitystreams\"" | "application/ld+json;profile=\"https://www.w3.org/ns/activitystreams\""
| "application/activity+json" | "application/activity+json"
| "application/ld+json" => Outcome::Success(ApRequest), | "application/ld+json" => Outcome::Success(ApRequest),
"text/html" => Outcome::Forward(true), "text/html" => Outcome::Forward(true),
_ => Outcome::Forward(false), _ => Outcome::Forward(false),
}
}) })
.fold(Outcome::Forward(false), |out, ct| { .fold(Outcome::Forward(false), |out, ct| {
if out.clone().forwarded().unwrap_or_else(|| out.is_success()) { if out.clone().forwarded().unwrap_or_else(|| out.is_success()) {
@@ -108,10 +124,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApRequest {
.unwrap_or(Outcome::Forward(())) .unwrap_or(Outcome::Forward(()))
} }
} }
pub fn broadcast<S, A, T, C>(sender: &S, act: A, to: Vec<T>, proxy: Option<reqwest::Proxy>) pub fn broadcast<S, A, T, C>(sender: &S, act: A, to: Vec<T>, proxy: Option<reqwest::Proxy>)
where where
S: sign::Signer, S: sign::Signer,
A: Activity, A: Activity + serde::Serialize,
T: inbox::AsActor<C>, T: inbox::AsActor<C>,
{ {
let boxes = to let boxes = to
@@ -130,59 +147,79 @@ where
.sign(sender) .sign(sender)
.expect("activity_pub::broadcast: signature error"); .expect("activity_pub::broadcast: signature error");
let mut rt = tokio::runtime::current_thread::Runtime::new() let client = if let Some(proxy) = proxy {
ClientBuilder::new().proxy(proxy)
} else {
ClientBuilder::new()
}
.connect_timeout(std::time::Duration::from_secs(5))
.build()
.expect("Can't build client");
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Error while initializing tokio runtime for federation"); .expect("Error while initializing tokio runtime for federation");
for inbox in boxes { rt.block_on(async {
let body = signed.to_string(); // TODO: should be determined dependent on database connections because
let mut headers = request::headers(); // after broadcasting, target instance sends request to this instance,
let url = Url::parse(&inbox); // and Plume accesses database at that time.
if url.is_err() { let capacity = 6;
warn!("Inbox is invalid URL: {:?}", &inbox); let (tx, rx) = flume::bounded::<RequestBuilder>(capacity);
continue; let mut handles = Vec::with_capacity(capacity);
for _ in 0..capacity {
let rx = rx.clone();
let handle = rt.spawn(async move {
while let Ok(request_builder) = rx.recv_async().await {
// After broadcasting, target instance sends request to this instance.
// Sleep here in order to reduce requests at once
sleep(Duration::from_millis(500)).await;
let _ = request_builder
.send()
.await
.map(move |r| {
if r.status().is_success() {
debug!("Successfully sent activity to inbox ({})", &r.url());
} else {
warn!("Error while sending to inbox ({:?})", &r)
}
debug!("Response: \"{:?}\"\n", r);
})
.map_err(|e| warn!("Error while sending to inbox ({:?})", e));
}
});
handles.push(handle);
} }
let url = url.unwrap(); for inbox in boxes {
if !url.has_host() { let body = signed.to_string();
warn!("Inbox doesn't have host: {:?}", &inbox); let mut headers = request::headers();
continue; let url = Url::parse(&inbox);
}; if url.is_err() {
let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable")); warn!("Inbox is invalid URL: {:?}", &inbox);
if host_header_value.is_err() { continue;
warn!("Header value is invalid: {:?}", url.host_str());
continue;
}
headers.insert("Host", host_header_value.unwrap());
headers.insert("Digest", request::Digest::digest(&body));
rt.spawn(
if let Some(proxy) = proxy.clone() {
ClientBuilder::new().proxy(proxy)
} else {
ClientBuilder::new()
} }
.connect_timeout(std::time::Duration::from_secs(5)) let url = url.unwrap();
.build() if !url.has_host() {
.expect("Can't build client") warn!("Inbox doesn't have host: {:?}", &inbox);
.post(&inbox) continue;
.headers(headers.clone()) };
.header( let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable"));
if host_header_value.is_err() {
warn!("Header value is invalid: {:?}", url.host_str());
continue;
}
headers.insert("Host", host_header_value.unwrap());
headers.insert("Digest", request::Digest::digest(&body));
headers.insert(
"Signature", "Signature",
request::signature(sender, &headers, ("post", url.path(), url.query())) request::signature(sender, &headers, ("post", url.path(), url.query()))
.expect("activity_pub::broadcast: request signature error"), .expect("activity_pub::broadcast: request signature error"),
) );
.body(body) let request_builder = client.post(&inbox).headers(headers.clone()).body(body);
.send() let _ = tx.send_async(request_builder).await;
.and_then(move |r| { }
if r.status().is_success() { drop(tx);
debug!("Successfully sent activity to inbox ({})", &inbox); join_all(handles).await;
} else { });
warn!("Error while sending to inbox ({:?})", &r)
}
r.into_body().concat2()
})
.map(move |response| debug!("Response: \"{:?}\"\n", response))
.map_err(|e| warn!("Error while sending to inbox ({:?})", e)),
);
}
rt.run().unwrap();
} }
#[derive(Shrinkwrap, Clone, Serialize, Deserialize)] #[derive(Shrinkwrap, Clone, Serialize, Deserialize)]
@@ -204,46 +241,193 @@ pub trait IntoId {
fn into_id(self) -> Id; fn into_id(self) -> Id;
} }
impl Link for Id {} #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ApSignature { pub struct ApSignature {
#[activitystreams(concrete(PublicKey), functional)] pub public_key: PublicKey,
pub public_key: Option<serde_json::Value>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PublicKey { pub struct PublicKey {
#[activitystreams(concrete(String), functional)] pub id: IriString,
pub id: Option<serde_json::Value>, pub owner: IriString,
pub public_key_pem: String,
#[activitystreams(concrete(String), functional)]
pub owner: Option<serde_json::Value>,
#[activitystreams(concrete(String), functional)]
pub public_key_pem: Option<serde_json::Value>,
} }
#[derive(Clone, Debug, Default, UnitString)] impl<U> UnparsedExtension<U> for ApSignature
#[activitystreams(Hashtag)] where
pub struct HashtagType; U: UnparsedMutExt,
{
type Error = serde_json::Error;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
Ok(ApSignature {
public_key: unparsed_mut.remove("publicKey")?,
})
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("publicKey", self.public_key)?;
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Hashtag { pub struct SourceProperty {
#[serde(rename = "type")] pub source: Source,
kind: HashtagType,
#[activitystreams(concrete(String), functional)]
pub href: Option<serde_json::Value>,
#[activitystreams(concrete(String), functional)]
pub name: Option<serde_json::Value>,
} }
#[derive(Clone, Debug, Default, Deserialize, Serialize)] impl<U> UnparsedExtension<U> for SourceProperty
where
U: UnparsedMutExt,
{
type Error = serde_json::Error;
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
Ok(SourceProperty {
source: unparsed_mut.remove("source")?,
})
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("source", self.source)?;
Ok(())
}
}
pub type CustomPerson = Ext1<ApActor<Person>, ApSignature>;
pub type CustomGroup = Ext2<ApActor<Group>, ApSignature, SourceProperty>;
kind!(HashtagType, Hashtag);
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct Hashtag {
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<IriString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<AnyString>,
#[serde(flatten)]
inner: Object<HashtagType>,
}
impl Hashtag {
pub fn new() -> Self {
Self {
href: None,
name: None,
inner: Object::new(),
}
}
pub fn extending(mut inner: Object<HashtagType>) -> Result<Self, serde_json::Error> {
let href = inner.remove("href")?;
let name = inner.remove("name")?;
Ok(Self { href, name, inner })
}
pub fn retracting(self) -> Result<Object<HashtagType>, serde_json::Error> {
let Self {
href,
name,
mut inner,
} = self;
inner.insert("href", href)?;
inner.insert("name", name)?;
Ok(inner)
}
}
pub trait AsHashtag: markers::Object {
fn hashtag_ref(&self) -> &Hashtag;
fn hashtag_mut(&mut self) -> &mut Hashtag;
}
pub trait HashtagExt: AsHashtag {
fn href(&self) -> Option<&IriString> {
self.hashtag_ref().href.as_ref()
}
fn set_href<T>(&mut self, href: T) -> &mut Self
where
T: Into<IriString>,
{
self.hashtag_mut().href = Some(href.into());
self
}
fn take_href(&mut self) -> Option<IriString> {
self.hashtag_mut().href.take()
}
fn delete_href(&mut self) -> &mut Self {
self.hashtag_mut().href = None;
self
}
fn name(&self) -> Option<&AnyString> {
self.hashtag_ref().name.as_ref()
}
fn set_name<T>(&mut self, name: T) -> &mut Self
where
T: Into<AnyString>,
{
self.hashtag_mut().name = Some(name.into());
self
}
fn take_name(&mut self) -> Option<AnyString> {
self.hashtag_mut().name.take()
}
fn delete_name(&mut self) -> &mut Self {
self.hashtag_mut().name = None;
self
}
}
impl Default for Hashtag {
fn default() -> Self {
Self::new()
}
}
impl AsHashtag for Hashtag {
fn hashtag_ref(&self) -> &Self {
self
}
fn hashtag_mut(&mut self) -> &mut Self {
self
}
}
impl Extends<HashtagType> for Hashtag {
type Error = serde_json::Error;
fn extends(base: Base<HashtagType>) -> Result<Self, Self::Error> {
let inner = Object::extends(base)?;
Self::extending(inner)
}
fn retracts(self) -> Result<Base<HashtagType>, Self::Error> {
let inner = self.retracting()?;
inner.retracts()
}
}
impl markers::Base for Hashtag {}
impl markers::Object for Hashtag {}
impl<T> HashtagExt for T where T: AsHashtag {}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Source { pub struct Source {
pub media_type: String, pub media_type: String,
@@ -251,13 +435,300 @@ pub struct Source {
pub content: String, pub content: String,
} }
impl Object for Source {} impl<U> UnparsedExtension<U> for Source
where
U: UnparsedMutExt,
{
type Error = serde_json::Error;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Properties)] fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
#[serde(rename_all = "camelCase")] Ok(Source {
pub struct Licensed { content: unparsed_mut.remove("content")?,
#[activitystreams(concrete(String), functional)] media_type: unparsed_mut.remove("mediaType")?,
pub license: Option<serde_json::Value>, })
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("content", self.content)?;
unparsed_mut.insert("mediaType", self.media_type)?;
Ok(())
}
} }
impl Object for Licensed {} #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Licensed {
pub license: Option<String>,
}
impl<U> UnparsedExtension<U> for Licensed
where
U: UnparsedMutExt,
{
type Error = serde_json::Error;
fn try_from_unparsed(unparsed_mut: &mut U) -> Result<Self, Self::Error> {
Ok(Licensed {
license: unparsed_mut.remove("license")?,
})
}
fn try_into_unparsed(self, unparsed_mut: &mut U) -> Result<(), Self::Error> {
unparsed_mut.insert("license", self.license)?;
Ok(())
}
}
pub type LicensedArticle = Ext1<ApObject<Article>, Licensed>;
pub trait ToAsString {
fn to_as_string(&self) -> Option<String>;
}
impl ToAsString for OneOrMany<&AnyString> {
fn to_as_string(&self) -> Option<String> {
self.as_as_str().map(|s| s.to_string())
}
}
trait AsAsStr {
fn as_as_str(&self) -> Option<&str>;
}
impl AsAsStr for OneOrMany<&AnyString> {
fn as_as_str(&self) -> Option<&str> {
self.iter().next().map(|prop| prop.as_str())
}
}
pub trait ToAsUri {
fn to_as_uri(&self) -> Option<String>;
}
impl ToAsUri for OneOrMany<AnyBase> {
fn to_as_uri(&self) -> Option<String> {
self.iter()
.next()
.and_then(|prop| prop.as_xsd_any_uri().map(|uri| uri.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use activitystreams::{
activity::{ActorAndObjectRef, Create},
object::kind::ArticleType,
};
use assert_json_diff::assert_json_eq;
use serde_json::{from_str, json, to_value};
#[test]
fn se_ap_signature() {
let ap_signature = ApSignature {
public_key: PublicKey {
id: "https://example.com/pubkey".parse().unwrap(),
owner: "https://example.com/owner".parse().unwrap(),
public_key_pem: "pubKeyPem".into(),
},
};
let expected = json!({
"publicKey": {
"id": "https://example.com/pubkey",
"owner": "https://example.com/owner",
"publicKeyPem": "pubKeyPem"
}
});
assert_json_eq!(to_value(ap_signature).unwrap(), expected);
}
#[test]
fn de_ap_signature() {
let value: ApSignature = from_str(
r#"
{
"publicKey": {
"id": "https://example.com/",
"owner": "https://example.com/",
"publicKeyPem": ""
}
}
"#,
)
.unwrap();
let expected = ApSignature {
public_key: PublicKey {
id: "https://example.com/".parse().unwrap(),
owner: "https://example.com/".parse().unwrap(),
public_key_pem: "".into(),
},
};
assert_eq!(value, expected);
}
#[test]
fn se_custom_person() {
let actor = ApActor::new("https://example.com/inbox".parse().unwrap(), Person::new());
let person = CustomPerson::new(
actor,
ApSignature {
public_key: PublicKey {
id: "https://example.com/pubkey".parse().unwrap(),
owner: "https://example.com/owner".parse().unwrap(),
public_key_pem: "pubKeyPem".into(),
},
},
);
let expected = json!({
"inbox": "https://example.com/inbox",
"type": "Person",
"publicKey": {
"id": "https://example.com/pubkey",
"owner": "https://example.com/owner",
"publicKeyPem": "pubKeyPem"
}
});
assert_eq!(to_value(person).unwrap(), expected);
}
#[test]
fn de_custom_group() {
let group = CustomGroup::new(
ApActor::new("https://example.com/inbox".parse().unwrap(), Group::new()),
ApSignature {
public_key: PublicKey {
id: "https://example.com/pubkey".parse().unwrap(),
owner: "https://example.com/owner".parse().unwrap(),
public_key_pem: "pubKeyPem".into(),
},
},
SourceProperty {
source: Source {
content: String::from("This is a *custom* group."),
media_type: String::from("text/markdown"),
},
},
);
let expected = json!({
"inbox": "https://example.com/inbox",
"type": "Group",
"publicKey": {
"id": "https://example.com/pubkey",
"owner": "https://example.com/owner",
"publicKeyPem": "pubKeyPem"
},
"source": {
"content": "This is a *custom* group.",
"mediaType": "text/markdown"
}
});
assert_eq!(to_value(group).unwrap(), expected);
}
#[test]
fn se_licensed_article() {
let object = ApObject::new(Article::new());
let licensed_article = LicensedArticle::new(
object,
Licensed {
license: Some("CC-0".into()),
},
);
let expected = json!({
"type": "Article",
"license": "CC-0",
});
assert_json_eq!(to_value(licensed_article).unwrap(), expected);
}
#[test]
fn de_licensed_article() {
let value: LicensedArticle = from_str(
r#"
{
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"license": "CC-0"
}
"#,
)
.unwrap();
let expected = json!({
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"license": "CC-0"
});
assert_eq!(to_value(value).unwrap(), expected);
}
#[test]
fn de_create_with_licensed_article() {
let create: Create = from_str(
r#"
{
"id": "https://plu.me/~/Blog/my-article",
"type": "Create",
"actor": "https://plu.me/@/Admin",
"to": "https://www.w3.org/ns/activitystreams#Public",
"object": {
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"license": "CC-0"
}
}
"#,
)
.unwrap();
let base = create.object_field_ref().as_single_base().unwrap();
let any_base = AnyBase::from_base(base.clone());
let value = any_base.extend::<LicensedArticle, ArticleType>().unwrap();
let expected = json!({
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"license": "CC-0"
});
assert_eq!(to_value(value).unwrap(), expected);
}
}
+4 -3
View File
@@ -1,10 +1,11 @@
use chrono::{offset::Utc, DateTime}; use chrono::{offset::Utc, DateTime};
use openssl::hash::{Hasher, MessageDigest}; use openssl::hash::{Hasher, MessageDigest};
use reqwest::{ use reqwest::{
blocking::{ClientBuilder, Response},
header::{ header::{
HeaderMap, HeaderValue, InvalidHeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT, HeaderMap, HeaderValue, InvalidHeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT,
}, },
ClientBuilder, Proxy, Response, Url, UrlError, Proxy, Url,
}; };
use std::ops::Deref; use std::ops::Deref;
use std::time::SystemTime; use std::time::SystemTime;
@@ -18,8 +19,8 @@ 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 { impl From<url::ParseError> for Error {
fn from(_err: UrlError) -> Self { fn from(_err: url::ParseError) -> Self {
Error() Error()
} }
} }
Executable → Regular
-2
View File
@@ -1,7 +1,5 @@
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
#[macro_use]
extern crate activitystreams_derive;
#[macro_use] #[macro_use]
extern crate shrinkwraprs; extern crate shrinkwraprs;
#[macro_use] #[macro_use]
+8 -20
View File
@@ -1,11 +1,8 @@
use heck::CamelCase; use heck::ToUpperCamelCase;
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::{ use rocket::http::uri::Uri;
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;
@@ -21,7 +18,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_camel_case() name.to_upper_camel_case()
.chars() .chars()
.filter(|c| c.is_alphanumeric()) .filter(|c| c.is_alphanumeric())
.collect() .collect()
@@ -80,19 +77,6 @@ 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,
@@ -287,7 +271,7 @@ pub fn md_to_html<'a>(
media_processor: Option<MediaProcessor<'a>>, media_processor: Option<MediaProcessor<'a>>,
) -> (String, HashSet<String>, HashSet<String>) { ) -> (String, HashSet<String>, HashSet<String>) {
let base_url = if let Some(base_url) = base_url { let base_url = if let Some(base_url) = base_url {
format!("//{}/", base_url) format!("https://{}/", base_url)
} else { } else {
"/".to_owned() "/".to_owned()
}; };
@@ -466,6 +450,10 @@ 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::*;
+7 -7
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-front" name = "plume-front"
version = "0.7.0" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
@@ -8,19 +8,19 @@ edition = "2018"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
gettext = { git = "https://github.com/Plume-org/gettext/", rev = "294c54d74c699fbc66502b480a37cc66c1daa7f3" } gettext = "0.4.0"
gettext-macros = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-macros = "0.6.1"
gettext-utils = { git = "https://github.com/Plume-org/gettext-macros/", rev = "a7c605f7edd6bfbfbfe7778026bfefd88d82db10" } gettext-utils = "0.1.0"
lazy_static = "1.3" lazy_static = "1.3"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
wasm-bindgen = "0.2.70" wasm-bindgen = "0.2.80"
js-sys = "0.3.47" js-sys = "0.3.57"
serde_derive = "1.0.123" serde_derive = "1.0.123"
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3.47" version = "0.3.57"
features = [ features = [
'console', 'console',
'ClipboardEvent', 'ClipboardEvent',
+3 -1
View File
@@ -397,7 +397,9 @@ fn init_editor() -> Result<(), EditorError> {
content_val.clone(), content_val.clone(),
false, false,
)?; )?;
content.set_inner_html(&content_val); if !content_val.is_empty() {
content.set_inner_html(&content_val);
}
// character counter // character counter
let character_counter = Closure::wrap(Box::new(mv!(content => move |_| { let character_counter = Closure::wrap(Box::new(mv!(content => move |_| {
+1
View File
@@ -23,6 +23,7 @@ init_i18n!(
en, en,
eo, eo,
es, es,
eu,
fa, fa,
fi, fi,
fr, fr,
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-macro" name = "plume-macro"
version = "0.7.0" version = "0.7.2"
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"
+18 -16
View File
@@ -1,39 +1,40 @@
[package] [package]
name = "plume-models" name = "plume-models"
version = "0.7.0" version = "0.7.2"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
activitypub = "0.1.1" ammonia = "3.2.0"
ammonia = "2.1.1" bcrypt = "0.12.1"
askama_escape = "0.1" guid-create = "0.2"
bcrypt = "0.10.1" itertools = "0.10.3"
guid-create = "0.1"
itertools = "0.8.0"
lazy_static = "1.0" lazy_static = "1.0"
ldap3 = "0.7.1" ldap3 = "0.10.4"
migrations_internals= "1.4.0" migrations_internals= "1.4.0"
openssl = "0.10.22" openssl = "0.10.40"
rocket = "0.4.6" rocket = "0.4.6"
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" } rocket_i18n = "0.4.1"
reqwest = "0.9" reqwest = "0.11.10"
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.81"
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.11.1" whatlang = "0.16.0"
shrinkwraprs = "0.2.1" shrinkwraprs = "0.3.0"
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.34"
riker = "0.4.2" riker = "0.4.2"
once_cell = "1.5.2" once_cell = "1.10.0"
lettre = "0.9.6"
native-tls = "0.2.10"
activitystreams = "0.7.0-alpha.18"
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
@@ -53,6 +54,7 @@ path = "../plume-common"
path = "../plume-macro" path = "../plume-macro"
[dev-dependencies] [dev-dependencies]
assert-json-diff = "2.0.1"
diesel_migrations = "1.3.0" diesel_migrations = "1.3.0"
[features] [features]
+315 -162
View File
@@ -1,12 +1,14 @@
use crate::{ use crate::{
ap_url, db_conn::DbConn, instance::*, medias::Media, posts::Post, safe_string::SafeString, db_conn::DbConn, instance::*, medias::Media, posts::Post, safe_string::SafeString,
schema::blogs, users::User, Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE, schema::blogs, users::User, Connection, Error, PlumeRocket, Result, CONFIG, ITEMS_PER_PAGE,
}; };
use activitypub::{ use activitystreams::{
actor::Group, actor::{ApActor, ApActorExt, AsApActor, Group},
base::AnyBase,
collection::{OrderedCollection, OrderedCollectionPage}, collection::{OrderedCollection, OrderedCollectionPage},
object::Image, iri_string::types::IriString,
CustomObject, object::{kind::ImageType, ApObject, Image, ObjectExt},
prelude::*,
}; };
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SaveChangesDsl};
@@ -18,14 +20,12 @@ 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, ActivityStream, ApSignature, CustomGroup, Id, IntoId, PublicKey, Source, SourceProperty,
ToAsString, ToAsUri,
}; };
use url::Url;
use webfinger::*; use webfinger::*;
pub type CustomGroup = CustomObject<ApSignature, Group>; #[derive(Queryable, Identifiable, Clone, AsChangeset, Debug)]
#[derive(Queryable, Identifiable, Clone, AsChangeset)]
#[changeset_options(treat_none_as_null = "true")] #[changeset_options(treat_none_as_null = "true")]
pub struct Blog { pub struct Blog {
pub id: i32, pub id: i32,
@@ -161,104 +161,120 @@ impl Blog {
} }
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> { pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
let mut blog = Group::default(); let mut blog = ApActor::new(self.inbox_url.parse()?, Group::new());
blog.ap_actor_props blog.set_preferred_username(self.actor_id.clone());
.set_preferred_username_string(self.actor_id.clone())?; blog.set_name(self.title.clone());
blog.object_props.set_name_string(self.title.clone())?; blog.set_outbox(self.outbox_url.parse()?);
blog.ap_actor_props blog.set_summary(self.summary_html.to_string());
.set_outbox_string(self.outbox_url.clone())?; let source = SourceProperty {
blog.ap_actor_props source: Source {
.set_inbox_string(self.inbox_url.clone())?; content: self.summary.clone(),
blog.object_props media_type: String::from("text/markdown"),
.set_summary_string(self.summary_html.to_string())?; },
blog.ap_object_props.set_source_object(Source { };
content: self.summary.clone(),
media_type: String::from("text/markdown"),
})?;
let mut icon = Image::default(); let mut icon = Image::new();
icon.object_props.set_url_string( let _ = self.icon_id.map(|id| {
self.icon_id Media::get(conn, id).and_then(|m| {
.and_then(|id| Media::get(conn, id).and_then(|m| m.url()).ok()) let _ = m
.unwrap_or_default(), .url()
)?; .and_then(|url| url.parse::<IriString>().map_err(|_| Error::Url))
icon.object_props.set_attributed_to_link( .map(|url| icon.set_url(url));
self.icon_id icon.set_attributed_to(
.and_then(|id| { User::get(conn, m.owner_id)?
Media::get(conn, id) .into_id()
.and_then(|m| Ok(User::get(conn, m.owner_id)?.into_id())) .parse::<IriString>()?,
.ok() );
}) Ok(())
.unwrap_or_else(|| Id::new(String::new())), })
)?; });
blog.object_props.set_icon_object(icon)?; blog.set_icon(icon.into_any_base()?);
let mut banner = Image::default(); let mut banner = Image::new();
banner.object_props.set_url_string( let _ = self.banner_id.map(|id| {
self.banner_id Media::get(conn, id).and_then(|m| {
.and_then(|id| Media::get(conn, id).and_then(|m| m.url()).ok()) let _ = m
.unwrap_or_default(), .url()
)?; .and_then(|url| url.parse::<IriString>().map_err(|_| Error::Url))
banner.object_props.set_attributed_to_link( .map(|url| banner.set_url(url));
self.banner_id banner.set_attributed_to(
.and_then(|id| { User::get(conn, m.owner_id)?
Media::get(conn, id) .into_id()
.and_then(|m| Ok(User::get(conn, m.owner_id)?.into_id())) .parse::<IriString>()?,
.ok() );
}) Ok(())
.unwrap_or_else(|| Id::new(String::new())), })
)?; });
blog.object_props.set_image_object(banner)?; blog.set_image(banner.into_any_base()?);
blog.object_props.set_id_string(self.ap_url.clone())?; blog.set_id(self.ap_url.parse()?);
let mut public_key = PublicKey::default(); let pub_key = PublicKey {
public_key.set_id_string(format!("{}#main-key", self.ap_url))?; id: format!("{}#main-key", self.ap_url).parse()?,
public_key.set_owner_string(self.ap_url.clone())?; owner: self.ap_url.parse()?,
public_key.set_public_key_pem_string(self.public_key.clone())?; public_key_pem: self.public_key.clone(),
let mut ap_signature = ApSignature::default(); };
ap_signature.set_public_key_publickey(public_key)?; let ap_signature = ApSignature {
public_key: pub_key,
};
Ok(CustomGroup::new(blog, ap_signature)) Ok(CustomGroup::new(blog, ap_signature, source))
} }
pub fn outbox(&self, conn: &Connection) -> Result<ActivityStream<OrderedCollection>> { pub fn outbox(&self, conn: &Connection) -> Result<ActivityStream<OrderedCollection>> {
let mut coll = OrderedCollection::default(); self.outbox_collection(conn).map(ActivityStream::new)
coll.collection_props.items = serde_json::to_value(self.get_activities(conn))?; }
coll.collection_props pub fn outbox_collection(&self, conn: &Connection) -> Result<OrderedCollection> {
.set_total_items_u64(self.get_activities(conn).len() as u64)?; let acts = self.get_activities(conn);
coll.collection_props let acts = acts
.set_first_link(Id::new(ap_url(&format!("{}?page=1", &self.outbox_url))))?; .iter()
coll.collection_props .filter_map(|value| AnyBase::from_arbitrary_json(value).ok())
.set_last_link(Id::new(ap_url(&format!( .collect::<Vec<AnyBase>>();
let n_acts = acts.len();
let mut coll = OrderedCollection::new();
coll.set_many_items(acts);
coll.set_total_items(n_acts as u64);
coll.set_first(format!("{}?page=1", &self.outbox_url).parse::<IriString>()?);
coll.set_last(
format!(
"{}?page={}", "{}?page={}",
&self.outbox_url, &self.outbox_url,
(self.get_activities(conn).len() as u64 + ITEMS_PER_PAGE as u64 - 1) as u64 (n_acts as u64 + ITEMS_PER_PAGE as u64 - 1) as u64 / ITEMS_PER_PAGE as u64
/ ITEMS_PER_PAGE as u64 )
))))?; .parse::<IriString>()?,
Ok(ActivityStream::new(coll)) );
Ok(coll)
} }
pub fn outbox_page( pub fn outbox_page(
&self, &self,
conn: &Connection, conn: &Connection,
(min, max): (i32, i32), (min, max): (i32, i32),
) -> Result<ActivityStream<OrderedCollectionPage>> { ) -> Result<ActivityStream<OrderedCollectionPage>> {
let mut coll = OrderedCollectionPage::default(); self.outbox_collection_page(conn, (min, max))
.map(ActivityStream::new)
}
pub fn outbox_collection_page(
&self,
conn: &Connection,
(min, max): (i32, i32),
) -> Result<OrderedCollectionPage> {
let mut coll = OrderedCollectionPage::new();
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.set_next(
"{}?page={}", format!("{}?page={}", &self.outbox_url, min / ITEMS_PER_PAGE + 1)
&self.outbox_url, .parse::<IriString>()?,
min / ITEMS_PER_PAGE + 1 );
)))?; coll.set_prev(
coll.collection_page_props.set_prev_link(Id::new(&format!( format!("{}?page={}", &self.outbox_url, min / ITEMS_PER_PAGE - 1)
"{}?page={}", .parse::<IriString>()?,
&self.outbox_url, );
min / ITEMS_PER_PAGE - 1 coll.set_many_items(
)))?; acts.iter()
coll.collection_props.items = serde_json::to_value(acts)?; .filter_map(|value| AnyBase::from_arbitrary_json(value).ok()),
Ok(ActivityStream::new(coll)) );
Ok(coll)
} }
fn get_activities(&self, _conn: &Connection) -> Vec<serde_json::Value> { fn get_activities(&self, _conn: &Connection) -> Vec<serde_json::Value> {
vec![] vec![]
@@ -354,9 +370,90 @@ impl FromId<DbConn> for Blog {
} }
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 (name, outbox_url, inbox_url) = {
let inst = url.host_str().ok_or(Error::Url)?; let actor = acct.ap_actor_ref();
let instance = Instance::find_by_domain(conn, inst).or_else(|_| { let name = actor
.preferred_username()
.ok_or(Error::MissingApProperty)?
.to_string();
if name.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
return Err(Error::InvalidValue);
}
(
name,
actor.outbox()?.ok_or(Error::MissingApProperty)?.to_string(),
actor.inbox()?.to_string(),
)
};
let mut new_blog = NewBlog {
actor_id: name.to_string(),
outbox_url,
inbox_url,
public_key: acct.ext_one.public_key.public_key_pem.to_string(),
private_key: None,
theme: None,
..NewBlog::default()
};
let object = ApObject::new(acct.inner);
new_blog.title = object
.name()
.and_then(|name| name.to_as_string())
.unwrap_or(name);
new_blog.summary_html = SafeString::new(
&object
.summary()
.and_then(|summary| summary.to_as_string())
.unwrap_or_default(),
);
let icon_id = object
.icon()
.and_then(|icons| {
icons.iter().next().and_then(|icon| {
let icon = icon.to_owned().extend::<Image, ImageType>().ok()??;
let owner = icon.attributed_to()?.to_as_uri()?;
Media::save_remote(
conn,
icon.url()?.to_as_uri()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
)
.ok()
})
})
.map(|m| m.id);
new_blog.icon_id = icon_id;
let banner_id = object
.image()
.and_then(|banners| {
banners.iter().next().and_then(|banner| {
let banner = banner.to_owned().extend::<Image, ImageType>().ok()??;
let owner = banner.attributed_to()?.to_as_uri()?;
Media::save_remote(
conn,
banner.url()?.to_as_uri()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
)
.ok()
})
})
.map(|m| m.id);
new_blog.banner_id = banner_id;
new_blog.summary = acct.ext_two.source.content;
let any_base = AnyBase::from_extended(object)?;
let id = any_base.id().ok_or(Error::MissingApProperty)?;
new_blog.ap_url = id.to_string();
let inst = id
.authority_components()
.ok_or(Error::Url)?
.host()
.to_string();
let instance = Instance::find_by_domain(conn, &inst).or_else(|_| {
Instance::insert( Instance::insert(
conn, conn,
NewInstance { NewInstance {
@@ -373,75 +470,9 @@ impl FromId<DbConn> for Blog {
}, },
) )
})?; })?;
let icon_id = acct new_blog.instance_id = instance.id;
.object
.object_props
.icon_image()
.ok()
.and_then(|icon| {
let owner = icon.object_props.attributed_to_link::<Id>().ok()?;
Media::save_remote(
conn,
icon.object_props.url_string().ok()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
)
.ok()
})
.map(|m| m.id);
let banner_id = acct Blog::insert(conn, new_blog)
.object
.object_props
.image_image()
.ok()
.and_then(|banner| {
let owner = banner.object_props.attributed_to_link::<Id>().ok()?;
Media::save_remote(
conn,
banner.object_props.url_string().ok()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?,
)
.ok()
})
.map(|m| m.id);
let name = acct.object.ap_actor_props.preferred_username_string()?;
if name.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
return Err(Error::InvalidValue);
}
Blog::insert(
conn,
NewBlog {
actor_id: name.clone(),
title: acct.object.object_props.name_string().unwrap_or(name),
outbox_url: acct.object.ap_actor_props.outbox_string()?,
inbox_url: acct.object.ap_actor_props.inbox_string()?,
summary: acct
.object
.ap_object_props
.source_object::<Source>()
.map(|s| s.content)
.unwrap_or_default(),
instance_id: instance.id,
ap_url: acct.object.object_props.id_string()?,
public_key: acct
.custom_props
.public_key_publickey()?
.public_key_pem_string()?,
private_key: None,
banner_id,
icon_id,
summary_html: SafeString::new(
&acct
.object
.object_props
.summary_string()
.unwrap_or_default(),
),
theme: None,
},
)
} }
fn get_sender() -> &'static dyn sign::Signer { fn get_sender() -> &'static dyn sign::Signer {
@@ -512,12 +543,14 @@ pub(crate) mod tests {
blog_authors::*, instance::tests as instance_tests, medias::NewMedia, tests::db, blog_authors::*, instance::tests as instance_tests, medias::NewMedia, tests::db,
users::tests as usersTests, Connection as Conn, users::tests as usersTests, Connection as Conn,
}; };
use assert_json_diff::assert_json_eq;
use diesel::Connection; use diesel::Connection;
use serde_json::to_value;
pub(crate) fn fill_database(conn: &Conn) -> (Vec<User>, Vec<Blog>) { pub(crate) fn fill_database(conn: &Conn) -> (Vec<User>, Vec<Blog>) {
instance_tests::fill_database(conn); instance_tests::fill_database(conn);
let users = usersTests::fill_database(conn); let users = usersTests::fill_database(conn);
let blog1 = Blog::insert( let mut blog1 = Blog::insert(
conn, conn,
NewBlog::new_local( NewBlog::new_local(
"BlogName".to_owned(), "BlogName".to_owned(),
@@ -590,6 +623,41 @@ pub(crate) mod tests {
}, },
) )
.unwrap(); .unwrap();
blog1.icon_id = Some(
Media::insert(
conn,
NewMedia {
file_path: "aaa.png".into(),
alt_text: String::new(),
is_remote: false,
remote_url: None,
sensitive: false,
content_warning: None,
owner_id: users[0].id,
},
)
.unwrap()
.id,
);
blog1.banner_id = Some(
Media::insert(
conn,
NewMedia {
file_path: "bbb.png".into(),
alt_text: String::new(),
is_remote: false,
remote_url: None,
sensitive: false,
content_warning: None,
owner_id: users[0].id,
},
)
.unwrap()
.id,
);
let _: Blog = blog1.save_changes(&*conn).unwrap();
(users, vec![blog1, blog2, blog3]) (users, vec![blog1, blog2, blog3])
} }
@@ -886,7 +954,6 @@ pub(crate) mod tests {
.id, .id,
); );
let _: Blog = blogs[0].save_changes(&**conn).unwrap(); let _: Blog = blogs[0].save_changes(&**conn).unwrap();
let ap_repr = blogs[0].to_activity(&conn).unwrap(); let ap_repr = blogs[0].to_activity(&conn).unwrap();
blogs[0].delete(&conn).unwrap(); blogs[0].delete(&conn).unwrap();
let blog = Blog::from_activity(&conn, ap_repr).unwrap(); let blog = Blog::from_activity(&conn, ap_repr).unwrap();
@@ -907,4 +974,90 @@ pub(crate) mod tests {
Ok(()) Ok(())
}) })
} }
#[test]
fn to_activity() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
let (_users, blogs) = fill_database(&conn);
let blog = &blogs[0];
let act = blog.to_activity(conn)?;
let expected = json!({
"icon": {
"attributedTo": "https://plu.me/@/admin/",
"type": "Image",
"url": "https://plu.me/aaa.png"
},
"id": "https://plu.me/~/BlogName/",
"image": {
"attributedTo": "https://plu.me/@/admin/",
"type": "Image",
"url": "https://plu.me/bbb.png"
},
"inbox": "https://plu.me/~/BlogName/inbox",
"name": "Blog name",
"outbox": "https://plu.me/~/BlogName/outbox",
"preferredUsername": "BlogName",
"publicKey": {
"id": "https://plu.me/~/BlogName/#main-key",
"owner": "https://plu.me/~/BlogName/",
"publicKeyPem": blog.public_key
},
"source": {
"content": "This is a small blog",
"mediaType": "text/markdown"
},
"summary": "",
"type": "Group"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn outbox_collection() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
let (_users, blogs) = fill_database(conn);
let blog = &blogs[0];
let act = blog.outbox_collection(conn)?;
let expected = json!({
"items": [],
"totalItems": 0,
"first": "https://plu.me/~/BlogName/outbox?page=1",
"last": "https://plu.me/~/BlogName/outbox?page=0",
"type": "OrderedCollection"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn outbox_collection_page() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
let (_users, blogs) = fill_database(conn);
let blog = &blogs[0];
let act = blog.outbox_collection_page(conn, (33, 36))?;
let expected = json!({
"next": "https://plu.me/~/BlogName/outbox?page=3",
"prev": "https://plu.me/~/BlogName/outbox?page=1",
"items": [],
"type": "OrderedCollectionPage"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
} }
+266 -120
View File
@@ -11,10 +11,15 @@ use crate::{
users::User, users::User,
Connection, Error, Result, CONFIG, Connection, Error, Result, CONFIG,
}; };
use activitypub::{ use activitystreams::{
activity::{Create, Delete}, activity::{Create, Delete},
link, base::{AnyBase, Base},
iri_string::types::IriString,
link::{self, kind::MentionType},
object::{Note, Tombstone}, object::{Note, Tombstone},
prelude::*,
primitives::OneOrMany,
time::OffsetDateTime,
}; };
use chrono::{self, NaiveDateTime}; use chrono::{self, NaiveDateTime};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
@@ -22,7 +27,7 @@ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
sign::Signer, sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY, IntoId, ToAsString, ToAsUri, PUBLIC_VISIBILITY,
}, },
utils, utils,
}; };
@@ -59,7 +64,7 @@ impl Comment {
insert!(comments, NewComment, |inserted, conn| { insert!(comments, NewComment, |inserted, conn| {
if inserted.ap_url.is_none() { if inserted.ap_url.is_none() {
inserted.ap_url = Some(format!( inserted.ap_url = Some(format!(
"{}comment/{}", "{}/comment/{}",
inserted.get_post(conn)?.ap_url, inserted.get_post(conn)?.ap_url,
inserted.id inserted.id
)); ));
@@ -115,29 +120,32 @@ impl Comment {
Some(Media::get_media_processor(conn, vec![&author])), Some(Media::get_media_processor(conn, vec![&author])),
); );
let mut note = Note::default(); let mut note = Note::new();
let to = vec![Id::new(PUBLIC_VISIBILITY.to_string())]; let to = vec![PUBLIC_VISIBILITY.parse::<IriString>()?];
note.object_props note.set_id(
.set_id_string(self.ap_url.clone().unwrap_or_default())?; self.ap_url
note.object_props .clone()
.set_summary_string(self.spoiler_text.clone())?; .unwrap_or_default()
note.object_props.set_content_string(html)?; .parse::<IriString>()?,
note.object_props );
.set_in_reply_to_link(Id::new(self.in_response_to_id.map_or_else( note.set_summary(self.spoiler_text.clone());
|| Ok(Post::get(conn, self.post_id)?.ap_url), note.set_content(html);
|id| Ok(Comment::get(conn, id)?.ap_url.unwrap_or_default()) as Result<String>, note.set_in_reply_to(self.in_response_to_id.map_or_else(
)?))?; || Post::get(conn, self.post_id).map(|post| post.ap_url),
note.object_props |id| Comment::get(conn, id).map(|comment| comment.ap_url.unwrap_or_default()),
.set_published_string(chrono::Utc::now().to_rfc3339())?; )?);
note.object_props.set_attributed_to_link(author.into_id())?; note.set_published(
note.object_props.set_to_link_vec(to)?; OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into())
note.object_props.set_tag_link_vec( .expect("OffsetDateTime"),
mentions );
.into_iter() note.set_attributed_to(author.into_id().parse::<IriString>()?);
.filter_map(|m| Mention::build_activity(conn, &m).ok()) note.set_many_tos(to);
.collect::<Vec<link::Mention>>(), note.set_many_tags(mentions.into_iter().filter_map(|m| {
)?; Mention::build_activity(conn, &m)
.map(|mention| mention.into_any_base().expect("Can convert"))
.ok()
}));
Ok(note) Ok(note)
} }
@@ -145,17 +153,26 @@ impl Comment {
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 note_clone = note.clone();
act.create_props.set_actor_link(author.into_id())?;
act.create_props.set_object_object(note.clone())?; let mut act = Create::new(
act.object_props.set_id_string(format!( author.into_id().parse::<IriString>()?,
"{}/activity", Base::retract(note)?.into_generic()?,
self.ap_url.clone().ok_or(Error::MissingApProperty)?, );
))?; act.set_id(
act.object_props format!(
.set_to_link_vec(note.object_props.to_link_vec::<Id>()?)?; "{}/activity",
act.object_props self.ap_url.clone().ok_or(Error::MissingApProperty)?,
.set_cc_link_vec(vec![Id::new(self.get_author(conn)?.followers_endpoint)])?; )
.parse::<IriString>()?,
);
act.set_many_tos(
note_clone
.to()
.iter()
.flat_map(|tos| tos.iter().map(|to| to.to_owned())),
);
act.set_many_ccs(vec![self.get_author(conn)?.followers_endpoint]);
Ok(act) Ok(act)
} }
@@ -180,20 +197,21 @@ impl Comment {
} }
pub fn build_delete(&self, conn: &Connection) -> Result<Delete> { pub fn build_delete(&self, conn: &Connection) -> Result<Delete> {
let mut act = Delete::default(); let mut tombstone = Tombstone::new();
act.delete_props tombstone.set_id(
.set_actor_link(self.get_author(conn)?.into_id())?; self.ap_url
.as_ref()
.ok_or(Error::MissingApProperty)?
.parse::<IriString>()?,
);
let mut tombstone = Tombstone::default(); let mut act = Delete::new(
tombstone self.get_author(conn)?.into_id().parse::<IriString>()?,
.object_props Base::retract(tombstone)?.into_generic()?,
.set_id_string(self.ap_url.clone().ok_or(Error::MissingApProperty)?)?; );
act.delete_props.set_object_object(tombstone)?;
act.object_props act.set_id(format!("{}#delete", self.ap_url.clone().unwrap()).parse::<IriString>()?);
.set_id_string(format!("{}#delete", self.ap_url.clone().unwrap()))?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY)])?;
Ok(act) Ok(act)
} }
@@ -210,108 +228,109 @@ 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()
.in_reply_to
.as_ref()
.ok_or(Error::MissingApProperty)? .ok_or(Error::MissingApProperty)?
.as_str() .iter()
.next()
.ok_or(Error::MissingApProperty)?
.id()
.ok_or(Error::MissingApProperty)?; .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.as_str());
let is_public = |v: &Option<serde_json::Value>| match v let is_public = |v: &Option<&OneOrMany<AnyBase>>| match v {
.as_ref() Some(one_or_many) => one_or_many.iter().any(|any_base| {
.unwrap_or(&serde_json::Value::Null) let id = any_base.id();
{ id.is_some() && id.unwrap() == PUBLIC_VISIBILITY
serde_json::Value::Array(v) => v }),
.iter() None => false,
.filter_map(serde_json::Value::as_str)
.any(|s| s == PUBLIC_VISIBILITY),
serde_json::Value::String(s) => s == PUBLIC_VISIBILITY,
_ => false,
}; };
let public_visibility = is_public(&note.object_props.to) let public_visibility = is_public(&note.to())
|| is_public(&note.object_props.bto) || is_public(&note.bto())
|| is_public(&note.object_props.cc) || is_public(&note.cc())
|| is_public(&note.object_props.bcc); || is_public(&note.bcc());
let summary = note.summary().and_then(|summary| summary.to_as_string());
let sensitive = summary.is_some();
let comm = Comment::insert( let comm = Comment::insert(
conn, conn,
NewComment { NewComment {
content: SafeString::new(&note.object_props.content_string()?), content: SafeString::new(
spoiler_text: note.object_props.summary_string().unwrap_or_default(), &note
ap_url: note.object_props.id_string().ok(), .content()
.ok_or(Error::MissingApProperty)?
.to_as_string()
.ok_or(Error::InvalidValue)?,
),
spoiler_text: summary.unwrap_or_default(),
ap_url: Some(
note.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_string(),
),
in_response_to_id: previous_comment.iter().map(|c| c.id).next(), in_response_to_id: previous_comment.iter().map(|c| c.id).next(),
post_id: previous_comment.map(|c| c.post_id).or_else(|_| { post_id: previous_comment.map(|c| c.post_id).or_else(|_| {
Ok(Post::find_by_ap_url(conn, previous_url)?.id) as Result<i32> Ok(Post::find_by_ap_url(conn, previous_url.as_str())?.id) as Result<i32>
})?, })?,
author_id: User::from_id( author_id: User::from_id(
conn, conn,
&note.object_props.attributed_to_link::<Id>()?, &note
.attributed_to()
.ok_or(Error::MissingApProperty)?
.to_as_uri()
.ok_or(Error::MissingApProperty)?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
.map_err(|(_, e)| e)? .map_err(|(_, e)| e)?
.id, .id,
sensitive: note.object_props.summary_string().is_ok(), sensitive,
public_visibility, public_visibility,
}, },
)?; )?;
// save mentions // save mentions
if let Some(serde_json::Value::Array(tags)) = note.object_props.tag.clone() { if let Some(tags) = note.tag() {
for tag in tags { let author_url = &Post::get(conn, comm.post_id)?.get_authors(conn)?[0].ap_url;
serde_json::from_value::<link::Mention>(tag) for tag in tags.iter() {
.map_err(Error::from) let m = tag.clone().extend::<link::Mention, MentionType>()?; // FIXME: Don't clone
.and_then(|m| { if m.is_none() {
let author = &Post::get(conn, comm.post_id)?.get_authors(conn)?[0]; continue;
let not_author = m.link_props.href_string()? != author.ap_url.clone(); }
Mention::from_activity(conn, &m, comm.id, false, not_author) let m = m.unwrap();
}) let not_author = m.href().ok_or(Error::MissingApProperty)? != author_url;
.ok(); let _ = Mention::from_activity(conn, &m, comm.id, false, not_author);
} }
} }
comm comm
}; };
if !comm.public_visibility { if !comm.public_visibility {
let receivers_ap_url = |v: Option<serde_json::Value>| { let mut receiver_ids = HashSet::new();
let filter = |e: serde_json::Value| { let mut receivers_id = |v: Option<&'_ OneOrMany<AnyBase>>| {
if let serde_json::Value::String(s) = e { if let Some(one_or_many) = v {
Some(s) for any_base in one_or_many.iter() {
} else { if let Some(id) = any_base.id() {
None receiver_ids.insert(id.to_string());
}
} }
};
match v.unwrap_or(serde_json::Value::Null) {
serde_json::Value::Array(v) => v,
v => vec![v],
} }
.into_iter()
.filter_map(filter)
}; };
let mut note = note; receivers_id(note.to());
receivers_id(note.cc());
receivers_id(note.bto());
receivers_id(note.bcc());
let to = receivers_ap_url(note.object_props.to.take()); let receivers_ap_url = receiver_ids
let cc = receivers_ap_url(note.object_props.cc.take());
let bto = receivers_ap_url(note.object_props.bto.take());
let bcc = receivers_ap_url(note.object_props.bcc.take());
let receivers_ap_url = to
.chain(cc)
.chain(bto)
.chain(bcc)
.collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
.into_iter() .into_iter()
.map(|v| { .flat_map(|v| {
if let Ok(user) = User::from_id(conn, &v, None, CONFIG.proxy()) { if let Ok(user) = User::from_id(conn, v.as_ref(), None, CONFIG.proxy()) {
vec![user] vec![user]
} else { } else {
vec![] // TODO try to fetch collection vec![] // TODO try to fetch collection
} }
}) })
.flatten()
.filter(|u| u.get_instance(conn).map(|i| i.local).unwrap_or(false)) .filter(|u| u.get_instance(conn).map(|i| i.local).unwrap_or(false))
.collect::<HashSet<User>>(); //remove duplicates (prevent db error) .collect::<HashSet<User>>(); //remove duplicates (prevent db error)
@@ -403,10 +422,34 @@ impl CommentTree {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::blogs::Blog;
use crate::inbox::{inbox, tests::fill_database, InboxResult}; use crate::inbox::{inbox, tests::fill_database, InboxResult};
use crate::safe_string::SafeString; use crate::safe_string::SafeString;
use crate::tests::db; use crate::tests::{db, format_datetime};
use assert_json_diff::assert_json_eq;
use diesel::Connection; use diesel::Connection;
use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Comment, Vec<Post>, Vec<User>, Vec<Blog>) {
let (posts, users, blogs) = fill_database(&conn);
let comment = Comment::insert(
conn,
NewComment {
content: SafeString::new("My comment, mentioning to @user"),
in_response_to_id: None,
post_id: posts[0].id,
author_id: users[0].id,
ap_url: None,
sensitive: true,
spoiler_text: "My CW".into(),
public_visibility: true,
},
)
.unwrap();
(comment, posts, users, blogs)
}
// creates a post, get it's Create activity, delete the post, // creates a post, get it's Create activity, delete the post,
// "send" the Create to the inbox, and check it works // "send" the Create to the inbox, and check it works
@@ -414,30 +457,77 @@ mod tests {
fn self_federation() { fn self_federation() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (posts, users, _) = fill_database(&conn); let (original_comm, posts, users, _blogs) = prepare_activity(&conn);
let act = original_comm.create_activity(&conn).unwrap();
let original_comm = Comment::insert( assert_json_eq!(to_value(&act).unwrap(), json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": format!("https://plu.me/~/BlogName/testing/comment/{}/activity", original_comm.id),
"object": {
"attributedTo": "https://plu.me/@/admin/",
"content": r###"<p dir="auto">My comment, mentioning to <a href="https://plu.me/@/user/" title="user">@user</a></p>
"###,
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", original_comm.id),
"inReplyTo": "https://plu.me/~/BlogName/testing",
"published": format_datetime(&original_comm.creation_date),
"summary": "My CW",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create",
}));
let reply = Comment::insert(
conn, conn,
NewComment { NewComment {
content: SafeString::new("My comment"), content: SafeString::new(""),
in_response_to_id: None, in_response_to_id: Some(original_comm.id),
post_id: posts[0].id, post_id: posts[0].id,
author_id: users[0].id, author_id: users[1].id,
ap_url: None, ap_url: None,
sensitive: true, sensitive: false,
spoiler_text: "My CW".into(), spoiler_text: "".into(),
public_visibility: true, public_visibility: true,
}, },
) )
.unwrap(); .unwrap();
let act = original_comm.create_activity(&conn).unwrap(); let reply_act = reply.create_activity(&conn).unwrap();
assert_json_eq!(to_value(&reply_act).unwrap(), json!({
"actor": "https://plu.me/@/user/",
"cc": ["https://plu.me/@/user/followers"],
"id": format!("https://plu.me/~/BlogName/testing/comment/{}/activity", reply.id),
"object": {
"attributedTo": "https://plu.me/@/user/",
"content": "",
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", reply.id),
"inReplyTo": format!("https://plu.me/~/BlogName/testing/comment/{}", original_comm.id),
"published": format_datetime(&reply.creation_date),
"summary": "",
"tag": [],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
}));
inbox( inbox(
&conn, &conn,
serde_json::to_value(original_comm.build_delete(&conn).unwrap()).unwrap(), serde_json::to_value(original_comm.build_delete(&conn).unwrap()).unwrap(),
) )
.unwrap(); .unwrap();
match inbox(&conn, serde_json::to_value(act).unwrap()).unwrap() { match inbox(&conn, to_value(act).unwrap()).unwrap() {
InboxResult::Commented(c) => { InboxResult::Commented(c) => {
// TODO: one is HTML, the other markdown: assert_eq!(c.content, original_comm.content); // TODO: one is HTML, the other markdown: assert_eq!(c.content, original_comm.content);
assert_eq!(c.in_response_to_id, original_comm.in_response_to_id); assert_eq!(c.in_response_to_id, original_comm.in_response_to_id);
@@ -452,4 +542,60 @@ mod tests {
Ok(()) Ok(())
}) })
} }
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (comment, _posts, _users, _blogs) = prepare_activity(&conn);
let act = comment.to_activity(&conn)?;
let expected = json!({
"attributedTo": "https://plu.me/@/admin/",
"content": r###"<p dir="auto">My comment, mentioning to <a href="https://plu.me/@/user/" title="user">@user</a></p>
"###,
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", comment.id),
"inReplyTo": "https://plu.me/~/BlogName/testing",
"published": format_datetime(&comment.creation_date),
"summary": "My CW",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Note"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_delete() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (comment, _posts, _users, _blogs) = prepare_activity(&conn);
let act = comment.build_delete(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"id": format!("https://plu.me/~/BlogName/testing/comment/{}#delete", comment.id),
"object": {
"id": format!("https://plu.me/~/BlogName/testing/comment/{}", comment.id),
"type": "Tombstone"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Delete"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
} }
+31
View File
@@ -1,4 +1,6 @@
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;
@@ -15,12 +17,14 @@ 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, InvalidRocketConfig>,
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>,
} }
@@ -245,6 +249,31 @@ 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,
@@ -335,6 +364,7 @@ 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)),
@@ -347,6 +377,7 @@ 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(),
}; };
+143
View File
@@ -0,0 +1,143 @@
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()
}
}
+158 -46
View File
@@ -2,7 +2,12 @@ use crate::{
ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User, ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User,
Connection, Error, Result, CONFIG, Connection, Error, Result, CONFIG,
}; };
use activitypub::activity::{Accept, Follow as FollowAct, Undo}; use activitystreams::{
activity::{Accept, ActorAndObjectRef, Follow as FollowAct, Undo},
base::AnyBase,
iri_string::types::IriString,
prelude::*,
};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use plume_common::activity_pub::{ use plume_common::activity_pub::{
broadcast, broadcast,
@@ -53,15 +58,13 @@ impl Follow {
pub fn to_activity(&self, conn: &Connection) -> Result<FollowAct> { pub fn to_activity(&self, conn: &Connection) -> Result<FollowAct> {
let user = User::get(conn, self.follower_id)?; let user = User::get(conn, self.follower_id)?;
let target = User::get(conn, self.following_id)?; let target = User::get(conn, self.following_id)?;
let target_id = target.ap_url.parse::<IriString>()?;
let mut act = FollowAct::new(user.ap_url.parse::<IriString>()?, target_id.clone());
act.set_id(self.ap_url.parse::<IriString>()?);
act.set_many_tos(vec![target_id]);
act.set_many_ccs(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
let mut act = FollowAct::default();
act.follow_props.set_actor_link::<Id>(user.into_id())?;
act.follow_props
.set_object_link::<Id>(target.clone().into_id())?;
act.object_props.set_id_string(self.ap_url.clone())?;
act.object_props.set_to_link_vec(vec![target.into_id()])?;
act.object_props
.set_cc_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?;
Ok(act) Ok(act)
} }
@@ -94,28 +97,16 @@ impl Follow {
NewFollow { NewFollow {
follower_id: from_id, follower_id: from_id,
following_id: target_id, following_id: target_id,
ap_url: follow.object_props.id_string()?, ap_url: follow
.object_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.to_string(),
}, },
)?; )?;
res.notify(conn)?; res.notify(conn)?;
let mut accept = Accept::default(); let accept = res.build_accept(from, target, follow)?;
let accept_id = ap_url(&format!(
"{}/follow/{}/accept",
CONFIG.base_url.as_str(),
&res.id
));
accept.object_props.set_id_string(accept_id)?;
accept
.object_props
.set_to_link_vec(vec![from.clone().into_id()])?;
accept
.object_props
.set_cc_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?;
accept
.accept_props
.set_actor_link::<Id>(target.clone().into_id())?;
accept.accept_props.set_object_object(follow)?;
broadcast( broadcast(
&*target, &*target,
accept, accept,
@@ -125,18 +116,41 @@ impl Follow {
Ok(res) Ok(res)
} }
pub fn build_accept<A: Signer + IntoId + Clone, B: Clone + AsActor<T> + IntoId, T>(
&self,
from: &B,
target: &A,
follow: FollowAct,
) -> Result<Accept> {
let mut accept = Accept::new(
target.clone().into_id().parse::<IriString>()?,
AnyBase::from_extended(follow)?,
);
let accept_id = ap_url(&format!(
"{}/follows/{}/accept",
CONFIG.base_url.as_str(),
self.id
));
accept.set_id(accept_id.parse::<IriString>()?);
accept.set_many_tos(vec![from.clone().into_id().parse::<IriString>()?]);
accept.set_many_ccs(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
Ok(accept)
}
pub fn build_undo(&self, conn: &Connection) -> Result<Undo> { pub fn build_undo(&self, conn: &Connection) -> Result<Undo> {
let mut undo = Undo::default(); let mut undo = Undo::new(
undo.undo_props User::get(conn, self.follower_id)?
.set_actor_link(User::get(conn, self.follower_id)?.into_id())?; .ap_url
undo.object_props .parse::<IriString>()?,
.set_id_string(format!("{}/undo", self.ap_url))?; self.ap_url.parse::<IriString>()?,
undo.undo_props );
.set_object_link::<Id>(self.clone().into_id())?; undo.set_id(format!("{}/undo", self.ap_url).parse::<IriString>()?);
undo.object_props undo.set_many_tos(vec![User::get(conn, self.following_id)?
.set_to_link_vec(vec![User::get(conn, self.following_id)?.into_id()])?; .ap_url
undo.object_props .parse::<IriString>()?]);
.set_cc_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; undo.set_many_ccs(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
Ok(undo) Ok(undo)
} }
} }
@@ -148,11 +162,7 @@ impl AsObject<User, FollowAct, &DbConn> for User {
fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Follow> { fn activity(self, conn: &DbConn, actor: User, id: &str) -> Result<Follow> {
// Mastodon (at least) requires the full Follow object when accepting it, // Mastodon (at least) requires the full Follow object when accepting it,
// so we rebuilt it here // so we rebuilt it here
let mut follow = FollowAct::default(); let follow = FollowAct::new(actor.ap_url.parse::<IriString>()?, id.parse::<IriString>()?);
follow.object_props.set_id_string(id.to_string())?;
follow
.follow_props
.set_actor_link::<Id>(actor.clone().into_id())?;
Follow::accept_follow(conn, &actor, &self, follow, actor.id, self.id) Follow::accept_follow(conn, &actor, &self, follow, actor.id, self.id)
} }
} }
@@ -168,7 +178,11 @@ 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,
&follow.follow_props.actor_link::<Id>()?, follow
.actor_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -176,7 +190,11 @@ impl FromId<DbConn> for Follow {
let target = User::from_id( let target = User::from_id(
conn, conn,
&follow.follow_props.object_link::<Id>()?, follow
.object_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -219,8 +237,29 @@ impl IntoId for Follow {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{tests::db, users::tests as user_tests}; use crate::{tests::db, users::tests as user_tests, users::tests::fill_database};
use assert_json_diff::assert_json_eq;
use diesel::Connection; use diesel::Connection;
use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Follow, User, User, Vec<User>) {
let users = fill_database(conn);
let following = &users[1];
let follower = &users[2];
let mut follow = Follow::insert(
conn,
NewFollow {
follower_id: follower.id,
following_id: following.id,
ap_url: "".into(),
},
)
.unwrap();
// following.ap_url = format!("https://plu.me/follows/{}", follow.id);
follow.ap_url = format!("https://plu.me/follows/{}", follow.id);
(follow, following.to_owned(), follower.to_owned(), users)
}
#[test] #[test]
fn test_id() { fn test_id() {
@@ -255,4 +294,77 @@ mod tests {
Ok(()) Ok(())
}) })
} }
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, _following, _follower, _users) = prepare_activity(&conn);
let act = follow.to_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}", follow.id),
"object": "https://plu.me/@/user/",
"to": ["https://plu.me/@/user/"],
"type": "Follow"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_accept() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, following, follower, _users) = prepare_activity(&conn);
let act = follow.build_accept(&follower, &following, follow.to_activity(&conn)?)?;
let expected = json!({
"actor": "https://plu.me/@/user/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://127.0.0.1:7878/follows/{}/accept", follow.id),
"object": {
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}", follow.id),
"object": "https://plu.me/@/user/",
"to": ["https://plu.me/@/user/"],
"type": "Follow"
},
"to": ["https://plu.me/@/other/"],
"type": "Accept"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (follow, _following, _follower, _users) = prepare_activity(&conn);
let act = follow.build_undo(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/other/",
"cc": ["https://www.w3.org/ns/activitystreams#Public"],
"id": format!("https://plu.me/follows/{}/undo", follow.id),
"object": format!("https://plu.me/follows/{}", follow.id),
"to": ["https://plu.me/@/user/"],
"type": "Undo"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
} }
+8 -8
View File
@@ -1,4 +1,4 @@
use activitypub::activity::*; use activitystreams::activity::{Announce, Create, Delete, Follow, Like, Undo, Update};
use crate::{ use crate::{
comments::Comment, comments::Comment,
@@ -94,8 +94,8 @@ pub(crate) mod tests {
license: "WTFPL".to_owned(), license: "WTFPL".to_owned(),
creation_date: None, creation_date: None,
ap_url: format!("https://plu.me/~/{}/testing", blogs[0].actor_id), ap_url: format!("https://plu.me/~/{}/testing", blogs[0].actor_id),
subtitle: String::new(), subtitle: "Bye".to_string(),
source: String::new(), source: "Hello".to_string(),
cover_id: None, cover_id: None,
}, },
) )
@@ -268,7 +268,7 @@ pub(crate) mod tests {
"actor": users[0].ap_url, "actor": users[0].ap_url,
"object": { "object": {
"type": "Article", "type": "Article",
"id": "https://plu.me/~/Blog/my-article", "id": "https://plu.me/~/BlogName/testing",
"attributedTo": [users[0].ap_url, blogs[0].ap_url], "attributedTo": [users[0].ap_url, blogs[0].ap_url],
"content": "Hello.", "content": "Hello.",
"name": "My Article", "name": "My Article",
@@ -286,11 +286,11 @@ pub(crate) mod tests {
match super::inbox(&conn, act).unwrap() { match super::inbox(&conn, act).unwrap() {
super::InboxResult::Post(p) => { super::InboxResult::Post(p) => {
assert!(p.is_author(&conn, users[0].id).unwrap()); assert!(p.is_author(&conn, users[0].id).unwrap());
assert_eq!(p.source, "Hello.".to_owned()); assert_eq!(p.source, "Hello".to_owned());
assert_eq!(p.blog_id, blogs[0].id); assert_eq!(p.blog_id, blogs[0].id);
assert_eq!(p.content, SafeString::new("Hello.")); assert_eq!(p.content, SafeString::new("Hello"));
assert_eq!(p.subtitle, "Bye.".to_owned()); assert_eq!(p.subtitle, "Bye".to_owned());
assert_eq!(p.title, "My Article".to_owned()); assert_eq!(p.title, "Testing".to_owned());
} }
_ => panic!("Unexpected result"), _ => panic!("Unexpected result"),
}; };
Executable → Regular
+70 -6
View File
@@ -16,6 +16,9 @@ extern crate serde_json;
#[macro_use] #[macro_use]
extern crate tantivy; extern crate tantivy;
use activitystreams::iri_string;
pub use lettre;
pub use lettre::smtp;
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, request, sign};
use posts::PostEvent; use posts::PostEvent;
@@ -65,6 +68,7 @@ pub enum Error {
Url, Url,
Webfinger, Webfinger,
Expired, Expired,
UserAlreadyExists,
} }
impl From<bcrypt::BcryptError> for Error { impl From<bcrypt::BcryptError> for Error {
@@ -97,6 +101,12 @@ impl From<url::ParseError> for Error {
} }
} }
impl From<iri_string::validate::Error> for Error {
fn from(_: iri_string::validate::Error) -> Self {
Error::Url
}
}
impl From<serde_json::Error> for Error { impl From<serde_json::Error> for Error {
fn from(_: serde_json::Error) -> Self { fn from(_: serde_json::Error) -> Self {
Error::SerDe Error::SerDe
@@ -115,12 +125,9 @@ impl From<reqwest::header::InvalidHeaderValue> for Error {
} }
} }
impl From<activitypub::Error> for Error { impl From<activitystreams::checked::CheckError> for Error {
fn from(err: activitypub::Error) -> Self { fn from(_: activitystreams::checked::CheckError) -> Error {
match err { Error::MissingApProperty
activitypub::Error::NotFound => Error::MissingApProperty,
_ => Error::SerDe,
}
} }
} }
@@ -300,10 +307,38 @@ pub fn ap_url(url: &str) -> String {
format!("https://{}", url) format!("https://{}", url)
} }
pub trait SmtpNewWithAddr {
fn new_with_addr(
addr: (&str, u16),
) -> std::result::Result<smtp::SmtpClient, smtp::error::Error>;
}
impl SmtpNewWithAddr for smtp::SmtpClient {
// Stolen from lettre::smtp::SmtpClient::new_simple()
fn new_with_addr(addr: (&str, u16)) -> std::result::Result<Self, smtp::error::Error> {
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))
}
}
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
mod tests { mod tests {
use crate::{db_conn, migrations::IMPORTED_MIGRATIONS, Connection as Conn, CONFIG}; use crate::{db_conn, migrations::IMPORTED_MIGRATIONS, Connection as Conn, CONFIG};
use chrono::{naive::NaiveDateTime, Datelike, Timelike};
use diesel::r2d2::ConnectionManager; use diesel::r2d2::ConnectionManager;
use plume_common::utils::random_hex; use plume_common::utils::random_hex;
use std::env::temp_dir; use std::env::temp_dir;
@@ -336,6 +371,33 @@ mod tests {
pool pool
}; };
} }
#[cfg(feature = "postgres")]
pub(crate) fn format_datetime(dt: &NaiveDateTime) -> String {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
dt.timestamp_subsec_micros()
)
}
#[cfg(feature = "sqlite")]
pub(crate) fn format_datetime(dt: &NaiveDateTime) -> String {
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second()
)
}
} }
pub mod admin; pub mod admin;
@@ -347,6 +409,7 @@ 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;
@@ -367,6 +430,7 @@ 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;
+108 -35
View File
@@ -2,13 +2,18 @@ use crate::{
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*, db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*,
users::User, Connection, Error, Result, CONFIG, users::User, Connection, Error, Result, CONFIG,
}; };
use activitypub::activity; use activitystreams::{
activity::{ActorAndObjectRef, Like as LikeAct, Undo},
base::AnyBase,
iri_string::types::IriString,
prelude::*,
};
use chrono::NaiveDateTime; 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, sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY, PUBLIC_VISIBILITY,
}; };
#[derive(Clone, Queryable, Identifiable)] #[derive(Clone, Queryable, Identifiable)]
@@ -34,18 +39,16 @@ impl Like {
find_by!(likes, find_by_ap_url, ap_url as &str); find_by!(likes, find_by_ap_url, ap_url as &str);
find_by!(likes, find_by_user_on_post, user_id as i32, post_id as i32); find_by!(likes, find_by_user_on_post, user_id as i32, post_id as i32);
pub fn to_activity(&self, conn: &Connection) -> Result<activity::Like> { pub fn to_activity(&self, conn: &Connection) -> Result<LikeAct> {
let mut act = activity::Like::default(); let mut act = LikeAct::new(
act.like_props User::get(conn, self.user_id)?.ap_url.parse::<IriString>()?,
.set_actor_link(User::get(conn, self.user_id)?.into_id())?; Post::get(conn, self.post_id)?.ap_url.parse::<IriString>()?,
act.like_props );
.set_object_link(Post::get(conn, self.post_id)?.into_id())?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props act.set_many_ccs(vec![User::get(conn, self.user_id)?
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; .followers_endpoint
act.object_props.set_cc_link_vec(vec![Id::new( .parse::<IriString>()?]);
User::get(conn, self.user_id)?.followers_endpoint, act.set_id(self.ap_url.parse::<IriString>()?);
)])?;
act.object_props.set_id_string(self.ap_url.clone())?;
Ok(act) Ok(act)
} }
@@ -67,24 +70,22 @@ impl Like {
Ok(()) Ok(())
} }
pub fn build_undo(&self, conn: &Connection) -> Result<activity::Undo> { pub fn build_undo(&self, conn: &Connection) -> Result<Undo> {
let mut act = activity::Undo::default(); let mut act = Undo::new(
act.undo_props User::get(conn, self.user_id)?.ap_url.parse::<IriString>()?,
.set_actor_link(User::get(conn, self.user_id)?.into_id())?; AnyBase::from_extended(self.to_activity(conn)?)?,
act.undo_props.set_object_object(self.to_activity(conn)?)?; );
act.object_props act.set_id(format!("{}#delete", self.ap_url).parse::<IriString>()?);
.set_id_string(format!("{}#delete", self.ap_url))?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props act.set_many_ccs(vec![User::get(conn, self.user_id)?
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; .followers_endpoint
act.object_props.set_cc_link_vec(vec![Id::new( .parse::<IriString>()?]);
User::get(conn, self.user_id)?.followers_endpoint,
)])?;
Ok(act) Ok(act)
} }
} }
impl AsObject<User, activity::Like, &DbConn> for Post { impl AsObject<User, LikeAct, &DbConn> for Post {
type Error = Error; type Error = Error;
type Output = Like; type Output = Like;
@@ -106,19 +107,22 @@ impl AsObject<User, activity::Like, &DbConn> for Post {
impl FromId<DbConn> for Like { impl FromId<DbConn> for Like {
type Error = Error; type Error = Error;
type Object = activity::Like; type Object = LikeAct;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Like::find_by_ap_url(conn, id) Like::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, act: activity::Like) -> Result<Self> { fn from_activity(conn: &DbConn, act: LikeAct) -> Result<Self> {
let res = Like::insert( let res = Like::insert(
conn, conn,
NewLike { NewLike {
post_id: Post::from_id( post_id: Post::from_id(
conn, conn,
&act.like_props.object_link::<Id>()?, act.object_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -126,13 +130,19 @@ impl FromId<DbConn> for Like {
.id, .id,
user_id: User::from_id( user_id: User::from_id(
conn, conn,
&act.like_props.actor_link::<Id>()?, act.actor_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
.map_err(|(_, e)| e)? .map_err(|(_, e)| e)?
.id, .id,
ap_url: act.object_props.id_string()?, ap_url: act
.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_string(),
}, },
)?; )?;
res.notify(conn)?; res.notify(conn)?;
@@ -144,7 +154,7 @@ impl FromId<DbConn> for Like {
} }
} }
impl AsObject<User, activity::Undo, &DbConn> for Like { impl AsObject<User, Undo, &DbConn> for Like {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
@@ -165,8 +175,7 @@ impl AsObject<User, activity::Undo, &DbConn> for Like {
impl NewLike { impl NewLike {
pub fn new(p: &Post, u: &User) -> Self { pub fn new(p: &Post, u: &User) -> Self {
// TODO: this URL is not valid let ap_url = format!("{}like/{}", u.ap_url, p.ap_url);
let ap_url = format!("{}/like/{}", u.ap_url, p.ap_url);
NewLike { NewLike {
post_id: p.id, post_id: p.id,
user_id: u.id, user_id: u.id,
@@ -174,3 +183,67 @@ impl NewLike {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::diesel::Connection;
use crate::{inbox::tests::fill_database, tests::db};
use assert_json_diff::assert_json_eq;
use serde_json::{json, to_value};
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let like = Like::insert(&*conn, NewLike::new(post, user))?;
let act = like.to_activity(&conn).unwrap();
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing",
"object": "https://plu.me/~/BlogName/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Like",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let like = Like::insert(&*conn, NewLike::new(post, user))?;
let act = like.build_undo(&*conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing#delete",
"object": {
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/like/https://plu.me/~/BlogName/testing",
"object": "https://plu.me/~/BlogName/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Like",
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Undo",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}
+31 -26
View File
@@ -2,13 +2,12 @@ use crate::{
ap_url, db_conn::DbConn, instance::Instance, safe_string::SafeString, schema::medias, ap_url, db_conn::DbConn, instance::Instance, safe_string::SafeString, schema::medias,
users::User, Connection, Error, Result, CONFIG, users::User, Connection, Error, Result, CONFIG,
}; };
use activitypub::object::Image; use activitystreams::{object::Image, prelude::*};
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, request, ToAsString, ToAsUri},
utils::MediaProcessor, utils::{escape, MediaProcessor},
}; };
use std::{ use std::{
fs::{self, DirBuilder}, fs::{self, DirBuilder},
@@ -209,9 +208,9 @@ 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()
.url_string() .and_then(|url| url.to_as_uri())
.or(Err(Error::MissingApProperty))?; .ok_or(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().ok_or(Error::InvalidValue)?;
if !parent.is_dir() { if !parent.is_dir() {
@@ -232,11 +231,12 @@ impl Media {
let mut updated = false; let mut updated = false;
let alt_text = image let alt_text = image
.object_props .content()
.content_string() .and_then(|content| content.to_as_string())
.or(Err(Error::NotFound))?; .ok_or(Error::NotFound)?;
let sensitive = image.object_props.summary_string().is_ok(); let summary = image.summary().and_then(|summary| summary.to_as_string());
let content_warning = image.object_props.summary_string().ok(); let sensitive = summary.is_some();
let content_warning = summary;
if media.alt_text != alt_text { if media.alt_text != alt_text {
media.alt_text = alt_text; media.alt_text = alt_text;
updated = true; updated = true;
@@ -263,28 +263,25 @@ impl Media {
Ok(media) Ok(media)
}) })
.or_else(|_| { .or_else(|_| {
let summary = image.summary().and_then(|summary| summary.to_as_string());
Media::insert( Media::insert(
conn, conn,
NewMedia { NewMedia {
file_path: path.to_str().ok_or(Error::InvalidValue)?.to_string(), file_path: path.to_str().ok_or(Error::InvalidValue)?.to_string(),
alt_text: image alt_text: image
.object_props .content()
.content_string() .and_then(|content| content.to_as_string())
.or(Err(Error::NotFound))?, .ok_or(Error::NotFound)?,
is_remote: false, is_remote: false,
remote_url: None, remote_url: None,
sensitive: image.object_props.summary_string().is_ok(), sensitive: summary.is_some(),
content_warning: image.object_props.summary_string().ok(), content_warning: summary,
owner_id: User::from_id( owner_id: User::from_id(
conn, conn,
image &image
.object_props .attributed_to()
.attributed_to_link_vec::<Id>() .and_then(|attributed_to| attributed_to.to_as_uri())
.or(Err(Error::NotFound))? .ok_or(Error::MissingApProperty)?,
.into_iter()
.next()
.ok_or(Error::NotFound)?
.as_ref(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -401,7 +398,15 @@ pub(crate) mod tests {
pub(crate) fn clean(conn: &Conn) { pub(crate) fn clean(conn: &Conn) {
//used to remove files generated by tests //used to remove files generated by tests
for media in Media::list_all_medias(conn).unwrap() { for media in Media::list_all_medias(conn).unwrap() {
media.delete(conn).unwrap(); if let Some(err) = media.delete(conn).err() {
match &err {
Error::Io(e) => match e.kind() {
std::io::ErrorKind::NotFound => (),
_ => panic!("{:?}", err),
},
_ => panic!("{:?}", err),
}
}
} }
} }
+72 -11
View File
@@ -2,7 +2,11 @@ use crate::{
comments::Comment, db_conn::DbConn, notifications::*, posts::Post, schema::mentions, comments::Comment, db_conn::DbConn, notifications::*, posts::Post, schema::mentions,
users::User, Connection, Error, Result, users::User, Connection, Error, Result,
}; };
use activitypub::link; use activitystreams::{
base::BaseExt,
iri_string::types::IriString,
link::{self, LinkExt},
};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::inbox::AsActor; use plume_common::activity_pub::inbox::AsActor;
@@ -58,19 +62,17 @@ impl Mention {
pub fn build_activity(conn: &DbConn, ment: &str) -> Result<link::Mention> { pub fn build_activity(conn: &DbConn, ment: &str) -> Result<link::Mention> {
let user = User::find_by_fqn(conn, ment)?; let user = User::find_by_fqn(conn, ment)?;
let mut mention = link::Mention::default(); let mut mention = link::Mention::new();
mention.link_props.set_href_string(user.ap_url)?; mention.set_href(user.ap_url.parse::<IriString>()?);
mention.link_props.set_name_string(format!("@{}", ment))?; mention.set_name(format!("@{}", ment));
Ok(mention) Ok(mention)
} }
pub fn to_activity(&self, conn: &Connection) -> Result<link::Mention> { pub fn to_activity(&self, conn: &Connection) -> Result<link::Mention> {
let user = self.get_mentioned(conn)?; let user = self.get_mentioned(conn)?;
let mut mention = link::Mention::default(); let mut mention = link::Mention::new();
mention.link_props.set_href_string(user.ap_url.clone())?; mention.set_href(user.ap_url.parse::<IriString>()?);
mention mention.set_name(format!("@{}", user.fqn));
.link_props
.set_name_string(format!("@{}", user.fqn))?;
Ok(mention) Ok(mention)
} }
@@ -81,8 +83,8 @@ 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.href().ok_or(Error::NotFound)?.as_str();
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 {
Post::get(conn, inside).and_then(|post| { Post::get(conn, inside).and_then(|post| {
@@ -145,3 +147,62 @@ impl Mention {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::{inbox::tests::fill_database, tests::db, Error};
use assert_json_diff::assert_json_eq;
use diesel::Connection;
use serde_json::{json, to_value};
#[test]
fn build_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (_posts, users, _blogs) = fill_database(&conn);
let user = &users[0];
let name = &user.username;
let act = Mention::build_activity(&conn, name)?;
let expected = json!({
"href": "https://plu.me/@/admin/",
"name": "@admin",
"type": "Mention",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &users[0];
let mention = Mention::insert(
&conn,
NewMention {
mentioned_id: user.id,
post_id: Some(post.id),
comment_id: None,
},
)?;
let act = mention.to_activity(&conn)?;
let expected = json!({
"href": "https://plu.me/@/admin/",
"name": "@admin",
"type": "Mention",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}
+442 -209
View File
@@ -3,20 +3,24 @@ use crate::{
post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User,
Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN, Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN,
}; };
use activitypub::{ use activitystreams::{
activity::{Create, Delete, Update}, activity::{Create, Delete, Update},
link, base::{AnyBase, Base},
object::{Article, Image, Tombstone}, iri_string::types::IriString,
CustomObject, link::{self, kind::MentionType},
object::{kind::ImageType, ApObject, Article, AsApObject, Image, ObjectExt, Tombstone},
prelude::*,
time::OffsetDateTime,
}; };
use chrono::{NaiveDateTime, TimeZone, Utc}; use chrono::{NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
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, sign::Signer,
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY, Hashtag, HashtagType, Id, IntoId, Licensed, LicensedArticle, ToAsString, ToAsUri,
PUBLIC_VISIBILITY,
}, },
utils::{iri_percent_encode_seg, md_to_html}, utils::{iri_percent_encode_seg, md_to_html},
}; };
@@ -24,8 +28,6 @@ use riker::actors::{Publish, Tell};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
pub type LicensedArticle = CustomObject<Licensed, Article>;
static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| Mutex::new(HashMap::new())); static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| Mutex::new(HashMap::new()));
#[derive(Queryable, Identifiable, Clone, AsChangeset, Debug)] #[derive(Queryable, Identifiable, Clone, AsChangeset, Debug)]
@@ -67,15 +69,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, new: NewPost) -> Result<Self> { pub fn insert(conn: &Connection, mut 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 mut post = Self::last(conn)?; let 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();
@@ -353,92 +355,92 @@ impl Post {
.collect::<Vec<serde_json::Value>>(); .collect::<Vec<serde_json::Value>>();
mentions_json.append(&mut tags_json); mentions_json.append(&mut tags_json);
let mut article = Article::default(); let mut article = ApObject::new(Article::new());
article.object_props.set_name_string(self.title.clone())?; article.set_name(self.title.clone());
article.object_props.set_id_string(self.ap_url.clone())?; article.set_id(self.ap_url.parse::<IriString>()?);
let mut authors = self let mut authors = self
.get_authors(conn)? .get_authors(conn)?
.into_iter() .into_iter()
.map(|x| Id::new(x.ap_url)) .filter_map(|x| x.ap_url.parse::<IriString>().ok())
.collect::<Vec<Id>>(); .collect::<Vec<IriString>>();
authors.push(self.get_blog(conn)?.into_id()); // add the blog URL here too authors.push(self.get_blog(conn)?.ap_url.parse::<IriString>()?); // add the blog URL here too
article article.set_many_attributed_tos(authors);
.object_props article.set_content(self.content.get().clone());
.set_attributed_to_link_vec::<Id>(authors)?; let source = AnyBase::from_arbitrary_json(serde_json::json!({
article "content": self.source,
.object_props "mediaType": "text/markdown",
.set_content_string(self.content.get().clone())?; }))?;
article.ap_object_props.set_source_object(Source { article.set_source(source);
content: self.source.clone(), article.set_published(
media_type: String::from("text/markdown"), OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into())
})?; .expect("OffsetDateTime"),
article );
.object_props article.set_summary(&*self.subtitle);
.set_published_utctime(Utc.from_utc_datetime(&self.creation_date))?; article.set_many_tags(
article mentions_json
.object_props .iter()
.set_summary_string(self.subtitle.clone())?; .filter_map(|mention_json| AnyBase::from_arbitrary_json(mention_json).ok()),
article.object_props.tag = Some(json!(mentions_json)); );
if let Some(media_id) = self.cover_id { if let Some(media_id) = self.cover_id {
let media = Media::get(conn, media_id)?; let media = Media::get(conn, media_id)?;
let mut cover = Image::default(); let mut cover = Image::new();
cover.object_props.set_url_string(media.url()?)?; cover.set_url(media.url()?);
if media.sensitive { if media.sensitive {
cover cover.set_summary(media.content_warning.unwrap_or_default());
.object_props
.set_summary_string(media.content_warning.unwrap_or_default())?;
} }
cover.object_props.set_content_string(media.alt_text)?; cover.set_content(media.alt_text);
cover cover.set_many_attributed_tos(vec![User::get(conn, media.owner_id)?
.object_props .ap_url
.set_attributed_to_link_vec(vec![User::get(conn, media.owner_id)?.into_id()])?; .parse::<IriString>()?]);
article.object_props.set_icon_object(cover)?; article.set_icon(cover.into_any_base()?);
} }
article.object_props.set_url_string(self.ap_url.clone())?; article.set_url(self.ap_url.parse::<IriString>()?);
article article.set_many_tos(
.object_props to.into_iter()
.set_to_link_vec::<Id>(to.into_iter().map(Id::new).collect())?; .filter_map(|to| to.parse::<IriString>().ok())
article .collect::<Vec<IriString>>(),
.object_props );
.set_cc_link_vec::<Id>(cc.into_iter().map(Id::new).collect())?; article.set_many_ccs(
let mut license = Licensed::default(); cc.into_iter()
license.set_license_string(self.license.clone())?; .filter_map(|cc| cc.parse::<IriString>().ok())
.collect::<Vec<IriString>>(),
);
let license = Licensed {
license: Some(self.license.clone()),
};
Ok(LicensedArticle::new(article, license)) Ok(LicensedArticle::new(article, license))
} }
pub fn create_activity(&self, conn: &Connection) -> Result<Create> { pub fn create_activity(&self, conn: &Connection) -> Result<Create> {
let article = self.to_activity(conn)?; let article = self.to_activity(conn)?;
let mut act = Create::default(); let to = article.to().ok_or(Error::MissingApProperty)?.clone();
act.object_props let cc = article.cc().ok_or(Error::MissingApProperty)?.clone();
.set_id_string(format!("{}activity", self.ap_url))?; let mut act = Create::new(
act.object_props self.get_authors(conn)?[0].ap_url.parse::<IriString>()?,
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?; Base::retract(article)?.into_generic()?,
act.object_props );
.set_cc_link_vec::<Id>(article.object.object_props.cc_link_vec()?)?; act.set_id(format!("{}/activity", self.ap_url).parse::<IriString>()?);
act.create_props act.set_many_tos(to);
.set_actor_link(Id::new(self.get_authors(conn)?[0].clone().ap_url))?; act.set_many_ccs(cc);
act.create_props.set_object_object(article)?;
Ok(act) Ok(act)
} }
pub fn update_activity(&self, conn: &Connection) -> Result<Update> { pub fn update_activity(&self, conn: &Connection) -> Result<Update> {
let article = self.to_activity(conn)?; let article = self.to_activity(conn)?;
let mut act = Update::default(); let to = article.to().ok_or(Error::MissingApProperty)?.clone();
act.object_props.set_id_string(format!( let cc = article.cc().ok_or(Error::MissingApProperty)?.clone();
"{}/update-{}", let mut act = Update::new(
self.ap_url, self.get_authors(conn)?[0].ap_url.parse::<IriString>()?,
Utc::now().timestamp() Base::retract(article)?.into_generic()?,
))?; );
act.object_props act.set_id(
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?; format!("{}/update-{}", self.ap_url, Utc::now().timestamp()).parse::<IriString>()?,
act.object_props );
.set_cc_link_vec::<Id>(article.object.object_props.cc_link_vec()?)?; act.set_many_tos(to);
act.update_props act.set_many_ccs(cc);
.set_actor_link(Id::new(self.get_authors(conn)?[0].clone().ap_url))?;
act.update_props.set_object_object(article)?;
Ok(act) Ok(act)
} }
@@ -447,10 +449,8 @@ impl Post {
.into_iter() .into_iter()
.map(|m| { .map(|m| {
( (
m.link_props m.href()
.href_string() .and_then(|ap_url| User::find_by_ap_url(conn, ap_url.as_ref()).ok())
.ok()
.and_then(|ap_url| User::find_by_ap_url(conn, &ap_url).ok())
.map(|u| u.id), .map(|u| u.id),
m, m,
) )
@@ -485,7 +485,7 @@ impl Post {
pub fn update_tags(&self, conn: &Connection, tags: Vec<Hashtag>) -> Result<()> { pub fn update_tags(&self, conn: &Connection, tags: Vec<Hashtag>) -> Result<()> {
let tags_name = tags let tags_name = tags
.iter() .iter()
.filter_map(|t| t.name_string().ok()) .filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string()))
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let old_tags = Tag::for_post(&*conn, self.id)?; let old_tags = Tag::for_post(&*conn, self.id)?;
@@ -502,8 +502,9 @@ impl Post {
for t in tags { for t in tags {
if !t if !t
.name_string() .name
.map(|n| old_tags_name.contains(&n)) .as_ref()
.map(|n| old_tags_name.contains(n.as_str()))
.unwrap_or(true) .unwrap_or(true)
{ {
Tag::from_activity(conn, &t, self.id, false)?; Tag::from_activity(conn, &t, self.id, false)?;
@@ -521,7 +522,7 @@ impl Post {
pub fn update_hashtags(&self, conn: &Connection, tags: Vec<Hashtag>) -> Result<()> { pub fn update_hashtags(&self, conn: &Connection, tags: Vec<Hashtag>) -> Result<()> {
let tags_name = tags let tags_name = tags
.iter() .iter()
.filter_map(|t| t.name_string().ok()) .filter_map(|t| t.name.as_ref().map(|name| name.as_str().to_string()))
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let old_tags = Tag::for_post(&*conn, self.id)?; let old_tags = Tag::for_post(&*conn, self.id)?;
@@ -538,8 +539,9 @@ impl Post {
for t in tags { for t in tags {
if !t if !t
.name_string() .name
.map(|n| old_tags_name.contains(&n)) .as_ref()
.map(|n| old_tags_name.contains(n.as_str()))
.unwrap_or(true) .unwrap_or(true)
{ {
Tag::from_activity(conn, &t, self.id, true)?; Tag::from_activity(conn, &t, self.id, true)?;
@@ -566,18 +568,19 @@ impl Post {
} }
pub fn build_delete(&self, conn: &Connection) -> Result<Delete> { pub fn build_delete(&self, conn: &Connection) -> Result<Delete> {
let mut act = Delete::default(); let mut tombstone = Tombstone::new();
act.delete_props tombstone.set_id(self.ap_url.parse()?);
.set_actor_link(self.get_authors(conn)?[0].clone().into_id())?;
let mut tombstone = Tombstone::default(); let mut act = Delete::new(
tombstone.object_props.set_id_string(self.ap_url.clone())?; self.get_authors(conn)?[0]
act.delete_props.set_object_object(tombstone)?; .clone()
.into_id()
.parse::<IriString>()?,
Base::retract(tombstone)?.into_generic()?,
);
act.object_props act.set_id(format!("{}#delete", self.ap_url).parse()?);
.set_id_string(format!("{}#delete", self.ap_url))?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY)])?;
Ok(act) Ok(act)
} }
@@ -621,47 +624,82 @@ impl FromId<DbConn> for Post {
} }
fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> { fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> {
let conn = conn; let license = article.ext_one.license.unwrap_or_default();
let license = article.custom_props.license_string().unwrap_or_default(); let article = article.inner;
let article = article.object;
let (blog, authors) = article let (blog, authors) = article
.object_props .ap_object_ref()
.attributed_to_link_vec::<Id>()? .attributed_to()
.into_iter() .ok_or(Error::MissingApProperty)?
.iter()
.fold((None, vec![]), |(blog, mut authors), link| { .fold((None, vec![]), |(blog, mut authors), link| {
let url = link; if let Some(url) = link.id() {
match User::from_id(conn, &url, None, CONFIG.proxy()) { match User::from_id(conn, url.as_str(), None, CONFIG.proxy()) {
Ok(u) => { Ok(u) => {
authors.push(u); authors.push(u);
(blog, authors) (blog, authors)
}
Err(_) => (
blog.or_else(|| {
Blog::from_id(conn, url.as_str(), None, CONFIG.proxy()).ok()
}),
authors,
),
} }
Err(_) => ( } else {
blog.or_else(|| Blog::from_id(conn, &url, None, CONFIG.proxy()).ok()), // logically, url possible to be an object without id proprty like {"type":"Person", "name":"Sally"} but we ignore the case
authors, (blog, authors)
),
} }
}); });
let cover = article let cover = article.icon().and_then(|icon| {
.object_props icon.iter().next().and_then(|img| {
.icon_object::<Image>() let image = img.to_owned().extend::<Image, ImageType>().ok()??;
.ok() Media::from_activity(conn, &image).ok().map(|m| m.id)
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)); })
});
let title = article.object_props.name_string()?; let title = article
.name()
.and_then(|name| name.to_as_string())
.ok_or(Error::MissingApProperty)?;
let id = AnyBase::from_extended(article.clone()) // FIXME: Don't clone
.ok()
.ok_or(Error::MissingApProperty)?
.id()
.map(|id| id.to_string());
let ap_url = article let ap_url = article
.object_props .url()
.url_string() .and_then(|url| url.to_as_uri().or(id))
.or_else(|_| article.object_props.id_string())?; .ok_or(Error::MissingApProperty)?;
let source = article
.source()
.and_then(|s| {
serde_json::to_value(s).ok().and_then(|obj| {
if !obj.is_object() {
return None;
}
obj.get("content")
.and_then(|content| content.as_str().map(|c| c.to_string()))
})
})
.unwrap_or_default();
let post = Post::from_db(conn, &ap_url) let post = Post::from_db(conn, &ap_url)
.and_then(|mut post| { .and_then(|mut post| {
let mut updated = false; let mut updated = false;
let slug = Self::slug(&title); let slug = Self::slug(&title);
let content = SafeString::new(&article.object_props.content_string()?); let content = SafeString::new(
let subtitle = article.object_props.summary_string()?; &article
let source = article.ap_object_props.source_object::<Source>()?.content; .content()
.and_then(|content| content.to_as_string())
.ok_or(Error::MissingApProperty)?,
);
let subtitle = article
.summary()
.and_then(|summary| summary.to_as_string())
.ok_or(Error::MissingApProperty)?;
if post.slug != slug { if post.slug != slug {
post.slug = slug.to_string(); post.slug = slug.to_string();
updated = true; updated = true;
@@ -683,7 +721,7 @@ impl FromId<DbConn> for Post {
updated = true; updated = true;
} }
if post.source != source { if post.source != source {
post.source = source; post.source = source.clone();
updated = true; updated = true;
} }
if post.cover_id != cover { if post.cover_id != cover {
@@ -704,14 +742,27 @@ impl FromId<DbConn> for Post {
blog_id: blog.ok_or(Error::NotFound)?.id, blog_id: blog.ok_or(Error::NotFound)?.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
.content()
.and_then(|content| content.to_as_string())
.ok_or(Error::MissingApProperty)?,
),
published: true, published: true,
license, license,
// FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields // FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields
ap_url, ap_url,
creation_date: Some(article.object_props.published_utctime()?.naive_utc()), creation_date: article.published().map(|published| {
subtitle: article.object_props.summary_string()?, let timestamp_secs = published.unix_timestamp();
source: article.ap_object_props.source_object::<Source>()?.content, let timestamp_nanos = published.unix_timestamp_nanos()
- (timestamp_secs as i128) * 1000i128 * 1000i128 * 1000i128;
NaiveDateTime::from_timestamp(timestamp_secs, timestamp_nanos as u32)
}),
subtitle: article
.summary()
.and_then(|summary| summary.to_as_string())
.ok_or(Error::MissingApProperty)?,
source,
cover_id: cover, cover_id: cover,
}, },
) )
@@ -735,22 +786,22 @@ impl FromId<DbConn> for Post {
.2 .2
.into_iter() .into_iter()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
if let Some(serde_json::Value::Array(tags)) = article.object_props.tag { if let Some(tags) = article.tag() {
for tag in tags { for tag in tags.iter() {
serde_json::from_value::<link::Mention>(tag.clone()) tag.clone()
.map(|m| Mention::from_activity(conn, &m, post.id, true, true)) .extend::<link::Mention, MentionType>() // FIXME: Don't clone
.map(|mention| {
mention.map(|m| Mention::from_activity(conn, &m, post.id, true, true))
})
.ok(); .ok();
serde_json::from_value::<Hashtag>(tag.clone()) tag.clone()
.map_err(Error::from) .extend::<Hashtag, HashtagType>() // FIXME: Don't clone
.and_then(|t| { .map(|hashtag| {
let tag_name = t.name_string()?; hashtag.and_then(|t| {
Ok(Tag::from_activity( let tag_name = t.name.clone()?.as_str().to_string();
conn, Tag::from_activity(conn, &t, post.id, hashtags.remove(&tag_name)).ok()
&t, })
post.id,
hashtags.remove(&tag_name),
))
}) })
.ok(); .ok();
} }
@@ -762,15 +813,15 @@ impl FromId<DbConn> for Post {
} }
fn get_sender() -> &'static dyn Signer { fn get_sender() -> &'static dyn Signer {
Instance::get_local_instance_user().expect("Failed to local instance user") Instance::get_local_instance_user().expect("Failed to get local instance user")
} }
} }
impl AsObject<User, Create, &DbConn> for Post { impl AsObject<User, Create, &DbConn> for Post {
type Error = Error; type Error = Error;
type Output = Post; type Output = Self;
fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Post> { fn activity(self, _conn: &DbConn, _actor: User, _id: &str) -> Result<Self::Output> {
// TODO: check that _actor is actually one of the author? // TODO: check that _actor is actually one of the author?
Ok(self) Ok(self)
} }
@@ -780,7 +831,7 @@ impl AsObject<User, Delete, &DbConn> for Post {
type Error = Error; type Error = Error;
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<Self::Output> {
let can_delete = self let can_delete = self
.get_authors(conn)? .get_authors(conn)?
.into_iter() .into_iter()
@@ -813,27 +864,54 @@ impl FromId<DbConn> for PostUpdate {
Err(Error::NotFound) Err(Error::NotFound)
} }
fn from_activity(conn: &DbConn, updated: LicensedArticle) -> Result<Self> { fn from_activity(conn: &DbConn, updated: Self::Object) -> Result<Self> {
Ok(PostUpdate { let mut post_update = PostUpdate {
ap_url: updated.object.object_props.id_string()?, ap_url: updated
title: updated.object.object_props.name_string().ok(), .ap_object_ref()
subtitle: updated.object.object_props.summary_string().ok(), .id_unchecked()
content: updated.object.object_props.content_string().ok(), .ok_or(Error::MissingApProperty)?
cover: updated .to_string(),
.object title: updated
.object_props .ap_object_ref()
.icon_object::<Image>() .name()
.ok() .and_then(|name| name.to_as_string()),
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)), subtitle: updated
source: updated .ap_object_ref()
.object .summary()
.ap_object_props .and_then(|summary| summary.to_as_string()),
.source_object::<Source>() content: updated
.ok() .ap_object_ref()
.map(|x| x.content), .content()
license: updated.custom_props.license_string().ok(), .and_then(|content| content.to_as_string()),
tags: updated.object.object_props.tag, cover: None,
}) source: updated.source().and_then(|s| {
serde_json::to_value(s).ok().and_then(|obj| {
if !obj.is_object() {
return None;
}
obj.get("content")
.and_then(|content| content.as_str().map(|c| c.to_string()))
})
}),
license: None,
tags: updated
.tag()
.and_then(|tags| serde_json::to_value(tags).ok()),
};
post_update.cover = updated.ap_object_ref().icon().and_then(|img| {
img.iter()
.next()
.and_then(|img| {
img.clone()
.extend::<Image, ImageType>()
.map(|img| img.and_then(|img| Media::from_activity(conn, &img).ok()))
.ok()
})
.and_then(|m| m.map(|m| m.id))
});
post_update.license = updated.ext_one.license;
Ok(post_update)
} }
fn get_sender() -> &'static dyn Signer { fn get_sender() -> &'static dyn Signer {
@@ -893,8 +971,12 @@ impl AsObject<User, Update, &DbConn> for PostUpdate {
serde_json::from_value::<Hashtag>(tag.clone()) serde_json::from_value::<Hashtag>(tag.clone())
.map_err(Error::from) .map_err(Error::from)
.and_then(|t| { .and_then(|t| {
let tag_name = t.name_string()?; let tag_name = t.name.as_ref().ok_or(Error::MissingApProperty)?;
if txt_hashtags.remove(&tag_name) { let tag_name_str = tag_name
.as_xsd_string()
.or_else(|| tag_name.as_rdf_lang_string().map(|rls| &*rls.value))
.ok_or(Error::MissingApProperty)?;
if txt_hashtags.remove(tag_name_str) {
hashtags.push(t); hashtags.push(t);
} else { } else {
tags.push(t); tags.push(t);
@@ -942,9 +1024,28 @@ impl From<PostEvent> for Arc<Post> {
mod tests { mod tests {
use super::*; use super::*;
use crate::inbox::{inbox, tests::fill_database, InboxResult}; use crate::inbox::{inbox, tests::fill_database, InboxResult};
use crate::mentions::{Mention, NewMention};
use crate::safe_string::SafeString; use crate::safe_string::SafeString;
use crate::tests::db; use crate::tests::{db, format_datetime};
use assert_json_diff::assert_json_eq;
use diesel::Connection; use diesel::Connection;
use serde_json::{json, to_value};
fn prepare_activity(conn: &DbConn) -> (Post, Mention, Vec<Post>, Vec<User>, Vec<Blog>) {
let (posts, users, blogs) = fill_database(conn);
let post = &posts[0];
let mentioned = &users[1];
let mention = Mention::insert(
&conn,
NewMention {
mentioned_id: mentioned.id,
post_id: Some(post.id),
comment_id: None,
},
)
.unwrap();
(post.to_owned(), mention.to_owned(), posts, users, blogs)
}
// creates a post, get it's Create activity, delete the post, // creates a post, get it's Create activity, delete the post,
// "send" the Create to the inbox, and check it works // "send" the Create to the inbox, and check it works
@@ -997,45 +1098,177 @@ mod tests {
} }
#[test] #[test]
fn licensed_article_serde() { fn to_activity() {
let mut article = Article::default(); let conn = db();
article.object_props.set_id_string("Yo".into()).unwrap(); conn.test_transaction::<_, Error, _>(|| {
let mut license = Licensed::default(); let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
license.set_license_string("WTFPL".into()).unwrap(); let act = post.to_activity(&conn)?;
let full_article = LicensedArticle::new(article, license);
let json = serde_json::to_value(full_article).unwrap(); let expected = json!({
let article_from_json: LicensedArticle = serde_json::from_value(json).unwrap(); "attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
assert_eq!( "cc": [],
"Yo", "content": "Hello",
&article_from_json.object.object_props.id_string().unwrap() "id": "https://plu.me/~/BlogName/testing",
); "license": "WTFPL",
assert_eq!( "name": "Testing",
"WTFPL", "published": format_datetime(&post.creation_date),
&article_from_json.custom_props.license_string().unwrap() "source": {
); "content": "Hello",
"mediaType": "text/markdown"
},
"summary": "Bye",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/BlogName/testing"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
} }
#[test] #[test]
fn licensed_article_deserialization() { fn create_activity() {
let json = json!({ let conn = db();
"type": "Article", conn.test_transaction::<_, Error, _>(|| {
"id": "https://plu.me/~/Blog/my-article", let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
"attributedTo": ["https://plu.me/@/Admin", "https://plu.me/~/Blog"], let act = post.create_activity(&conn)?;
"content": "Hello.",
"name": "My Article", let expected = json!({
"summary": "Bye.", "actor": "https://plu.me/@/admin/",
"source": { "cc": [],
"content": "Hello.", "id": "https://plu.me/~/BlogName/testing/activity",
"mediaType": "text/markdown" "object": {
}, "attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
"published": "2014-12-12T12:12:12Z", "cc": [],
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY] "content": "Hello",
"id": "https://plu.me/~/BlogName/testing",
"license": "WTFPL",
"name": "Testing",
"published": format_datetime(&post.creation_date),
"source": {
"content": "Hello",
"mediaType": "text/markdown"
},
"summary": "Bye",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/BlogName/testing"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Create"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn update_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
let act = post.update_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": [],
"id": "https://plu.me/~/BlogName/testing/update-",
"object": {
"attributedTo": ["https://plu.me/@/admin/", "https://plu.me/~/BlogName/"],
"cc": [],
"content": "Hello",
"id": "https://plu.me/~/BlogName/testing",
"license": "WTFPL",
"name": "Testing",
"published": format_datetime(&post.creation_date),
"source": {
"content": "Hello",
"mediaType": "text/markdown"
},
"summary": "Bye",
"tag": [
{
"href": "https://plu.me/@/user/",
"name": "@user",
"type": "Mention"
}
],
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Article",
"url": "https://plu.me/~/BlogName/testing"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Update"
});
let actual = to_value(act)?;
let id = actual["id"].to_string();
let (id_pre, id_post) = id.rsplit_once("-").unwrap();
assert_eq!(post.ap_url, "https://plu.me/~/BlogName/testing");
assert_eq!(
id_pre,
to_value("\"https://plu.me/~/BlogName/testing/update")
.unwrap()
.as_str()
.unwrap()
);
assert_eq!(id_post.len(), 11);
assert_eq!(
id_post.matches(char::is_numeric).collect::<String>().len(),
10
);
for (key, value) in actual.as_object().unwrap().into_iter() {
if key == "id" {
continue;
}
assert_json_eq!(value, expected.get(key).unwrap());
}
Ok(())
});
}
#[test]
fn build_delete() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (post, _mention, _posts, _users, _blogs) = prepare_activity(&conn);
let act = post.build_delete(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"id": "https://plu.me/~/BlogName/testing#delete",
"object": {
"id": "https://plu.me/~/BlogName/testing",
"type": "Tombstone"
},
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Delete"
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
}); });
let article: LicensedArticle = serde_json::from_value(json).unwrap();
assert_eq!(
"https://plu.me/~/Blog/my-article",
&article.object.object_props.id_string().unwrap()
);
} }
} }
+14 -6
View File
@@ -1,12 +1,16 @@
use crate::{ use crate::{
db_conn::{DbConn, DbPool}, db_conn::{DbConn, DbPool},
follows, follows,
posts::{LicensedArticle, Post}, posts::Post,
users::{User, UserEvent}, users::{User, UserEvent},
ACTOR_SYS, CONFIG, USER_CHAN, ACTOR_SYS, CONFIG, USER_CHAN,
}; };
use activitypub::activity::Create; use activitystreams::{
use plume_common::activity_pub::inbox::FromId; activity::{ActorAndObjectRef, Create},
base::AnyBase,
object::kind::ArticleType,
};
use plume_common::activity_pub::{inbox::FromId, LicensedArticle};
use riker::actors::{Actor, ActorFactoryArgs, ActorRefFactory, Context, Sender, Subscribe, Tell}; use riker::actors::{Actor, ActorFactoryArgs, ActorRefFactory, Context, Sender, Subscribe, Tell};
use std::sync::Arc; use std::sync::Arc;
use tracing::{error, info, warn}; use tracing::{error, info, warn};
@@ -68,13 +72,17 @@ fn fetch_and_cache_articles(user: &Arc<User>, conn: &DbConn) {
match create_acts { match create_acts {
Ok(create_acts) => { Ok(create_acts) => {
for create_act in create_acts { for create_act in create_acts {
match create_act.create_props.object_object::<LicensedArticle>() { match create_act.object_field_ref().as_single_base().map(|base| {
Ok(article) => { let any_base = AnyBase::from_base(base.clone()); // FIXME: Don't clone()
any_base.extend::<LicensedArticle, ArticleType>()
}) {
Some(Ok(Some(article))) => {
Post::from_activity(conn, article) Post::from_activity(conn, article)
.expect("Article from remote user couldn't be saved"); .expect("Article from remote user couldn't be saved");
info!("Fetched article from remote user"); info!("Fetched article from remote user");
} }
Err(e) => warn!("Error while fetching articles in background: {:?}", e), Some(Err(e)) => warn!("Error while fetching articles in background: {:?}", e),
_ => warn!("Error while fetching articles in background"),
} }
} }
} }
+104 -26
View File
@@ -2,13 +2,18 @@ use crate::{
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares, db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares,
timeline::*, users::User, Connection, Error, Result, CONFIG, timeline::*, users::User, Connection, Error, Result, CONFIG,
}; };
use activitypub::activity::{Announce, Undo}; use activitystreams::{
activity::{ActorAndObjectRef, Announce, Undo},
base::AnyBase,
iri_string::types::IriString,
prelude::*,
};
use chrono::NaiveDateTime; 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, sign::Signer,
Id, IntoId, PUBLIC_VISIBILITY, PUBLIC_VISIBILITY,
}; };
#[derive(Clone, Queryable, Identifiable)] #[derive(Clone, Queryable, Identifiable)]
@@ -61,16 +66,16 @@ impl Reshare {
} }
pub fn to_activity(&self, conn: &Connection) -> Result<Announce> { pub fn to_activity(&self, conn: &Connection) -> Result<Announce> {
let mut act = Announce::default(); let mut act = Announce::new(
act.announce_props User::get(conn, self.user_id)?.ap_url.parse::<IriString>()?,
.set_actor_link(User::get(conn, self.user_id)?.into_id())?; Post::get(conn, self.post_id)?.ap_url.parse::<IriString>()?,
act.announce_props );
.set_object_link(Post::get(conn, self.post_id)?.into_id())?; act.set_id(self.ap_url.parse::<IriString>()?);
act.object_props.set_id_string(self.ap_url.clone())?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props act.set_many_ccs(vec![self
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; .get_user(conn)?
act.object_props .followers_endpoint
.set_cc_link_vec(vec![Id::new(self.get_user(conn)?.followers_endpoint)])?; .parse::<IriString>()?]);
Ok(act) Ok(act)
} }
@@ -93,16 +98,16 @@ impl Reshare {
} }
pub fn build_undo(&self, conn: &Connection) -> Result<Undo> { pub fn build_undo(&self, conn: &Connection) -> Result<Undo> {
let mut act = Undo::default(); let mut act = Undo::new(
act.undo_props User::get(conn, self.user_id)?.ap_url.parse::<IriString>()?,
.set_actor_link(User::get(conn, self.user_id)?.into_id())?; AnyBase::from_extended(self.to_activity(conn)?)?,
act.undo_props.set_object_object(self.to_activity(conn)?)?; );
act.object_props act.set_id(format!("{}#delete", self.ap_url).parse::<IriString>()?);
.set_id_string(format!("{}#delete", self.ap_url))?; act.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
act.object_props act.set_many_ccs(vec![self
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY.to_string())])?; .get_user(conn)?
act.object_props .followers_endpoint
.set_cc_link_vec(vec![Id::new(self.get_user(conn)?.followers_endpoint)])?; .parse::<IriString>()?]);
Ok(act) Ok(act)
} }
@@ -143,7 +148,10 @@ impl FromId<DbConn> for Reshare {
NewReshare { NewReshare {
post_id: Post::from_id( post_id: Post::from_id(
conn, conn,
&act.announce_props.object_link::<Id>()?, act.object_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
@@ -151,13 +159,19 @@ impl FromId<DbConn> for Reshare {
.id, .id,
user_id: User::from_id( user_id: User::from_id(
conn, conn,
&act.announce_props.actor_link::<Id>()?, act.actor_field_ref()
.as_single_id()
.ok_or(Error::MissingApProperty)?
.as_str(),
None, None,
CONFIG.proxy(), CONFIG.proxy(),
) )
.map_err(|(_, e)| e)? .map_err(|(_, e)| e)?
.id, .id,
ap_url: act.object_props.id_string()?, ap_url: act
.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_string(),
}, },
)?; )?;
res.notify(conn)?; res.notify(conn)?;
@@ -191,7 +205,7 @@ impl AsObject<User, Undo, &DbConn> for Reshare {
impl NewReshare { impl NewReshare {
pub fn new(p: &Post, u: &User) -> Self { pub fn new(p: &Post, u: &User) -> Self {
let ap_url = format!("{}/reshare/{}", u.ap_url, p.ap_url); let ap_url = format!("{}reshare/{}", u.ap_url, p.ap_url);
NewReshare { NewReshare {
post_id: p.id, post_id: p.id,
user_id: u.id, user_id: u.id,
@@ -199,3 +213,67 @@ impl NewReshare {
} }
} }
} }
#[cfg(test)]
mod test {
use super::*;
use crate::diesel::Connection;
use crate::{inbox::tests::fill_database, tests::db};
use assert_json_diff::assert_json_eq;
use serde_json::{json, to_value};
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let reshare = Reshare::insert(&*conn, NewReshare::new(post, user))?;
let act = reshare.to_activity(&conn).unwrap();
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing",
"object": "https://plu.me/~/BlogName/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Announce",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn build_undo() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(&conn);
let post = &posts[0];
let user = &post.get_authors(&conn)?[0];
let reshare = Reshare::insert(&*conn, NewReshare::new(post, user))?;
let act = reshare.build_undo(&*conn)?;
let expected = json!({
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing#delete",
"object": {
"actor": "https://plu.me/@/admin/",
"cc": ["https://plu.me/@/admin/followers"],
"id": "https://plu.me/@/admin/reshare/https://plu.me/~/BlogName/testing",
"object": "https://plu.me/~/BlogName/testing",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Announce"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Undo",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
}
+14 -2
View File
@@ -73,16 +73,26 @@ table! {
user_id -> Int4, user_id -> Int4,
} }
} }
table! { table! {
email_blocklist(id){ email_blocklist (id) {
id -> Int4, id -> Int4,
email_address -> VarChar, email_address -> Text,
note -> Text, note -> Text,
notify_user -> Bool, notify_user -> Bool,
notification_text -> Text, notification_text -> Text,
} }
} }
table! {
email_signups (id) {
id -> Int4,
email -> Varchar,
token -> Varchar,
expiration_date -> Timestamp,
}
}
table! { table! {
follows (id) { follows (id) {
id -> Int4, id -> Int4,
@@ -306,6 +316,8 @@ allow_tables_to_appear_in_same_query!(
blogs, blogs,
comments, comments,
comment_seers, comment_seers,
email_blocklist,
email_signups,
follows, follows,
instances, instances,
likes, likes,
+1 -1
View File
@@ -57,7 +57,7 @@ impl<'a> WhitespaceTokenStream<'a> {
.filter(|&(_, ref c)| c.is_whitespace()) .filter(|&(_, ref c)| c.is_whitespace())
.map(|(offset, _)| offset) .map(|(offset, _)| offset)
.next() .next()
.unwrap_or_else(|| self.text.len()) .unwrap_or(self.text.len())
} }
} }
+72
View File
@@ -0,0 +1,72 @@
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(()),
}
}
}
+92 -16
View File
@@ -1,6 +1,7 @@
use crate::{ap_url, instance::Instance, schema::tags, Connection, Error, Result}; use crate::{ap_url, instance::Instance, schema::tags, Connection, Error, Result};
use activitystreams::iri_string::types::IriString;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
use plume_common::activity_pub::Hashtag; use plume_common::activity_pub::{Hashtag, HashtagExt};
#[derive(Clone, Identifiable, Queryable)] #[derive(Clone, Identifiable, Queryable)]
pub struct Tag { pub struct Tag {
@@ -25,13 +26,16 @@ impl Tag {
list_by!(tags, for_post, post_id as i32); list_by!(tags, for_post, post_id as i32);
pub fn to_activity(&self) -> Result<Hashtag> { pub fn to_activity(&self) -> Result<Hashtag> {
let mut ht = Hashtag::default(); let mut ht = Hashtag::new();
ht.set_href_string(ap_url(&format!( ht.set_href(
"{}/tag/{}", ap_url(&format!(
Instance::get_local()?.public_domain, "{}/tag/{}",
self.tag Instance::get_local()?.public_domain,
)))?; self.tag
ht.set_name_string(self.tag.clone())?; ))
.parse::<IriString>()?,
);
ht.set_name(self.tag.clone());
Ok(ht) Ok(ht)
} }
@@ -44,7 +48,7 @@ impl Tag {
Tag::insert( Tag::insert(
conn, conn,
NewTag { NewTag {
tag: tag.name_string()?, tag: tag.name().ok_or(Error::MissingApProperty)?.as_str().into(),
is_hashtag, is_hashtag,
post_id: post, post_id: post,
}, },
@@ -52,13 +56,16 @@ impl Tag {
} }
pub fn build_activity(tag: String) -> Result<Hashtag> { pub fn build_activity(tag: String) -> Result<Hashtag> {
let mut ht = Hashtag::default(); let mut ht = Hashtag::new();
ht.set_href_string(ap_url(&format!( ht.set_href(
"{}/tag/{}", ap_url(&format!(
Instance::get_local()?.public_domain, "{}/tag/{}",
tag Instance::get_local()?.public_domain,
)))?; tag
ht.set_name_string(tag)?; ))
.parse::<IriString>()?,
);
ht.set_name(tag);
Ok(ht) Ok(ht)
} }
@@ -69,3 +76,72 @@ impl Tag {
.map_err(Error::from) .map_err(Error::from)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::db;
use crate::{diesel::Connection, inbox::tests::fill_database};
use assert_json_diff::assert_json_eq;
use serde_json::to_value;
#[test]
fn from_activity() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
let (posts, _users, _blogs) = fill_database(conn);
let post_id = posts[0].id;
let mut ht = Hashtag::new();
ht.set_href(ap_url(&format!("https://plu.me/tag/a_tag")).parse::<IriString>()?);
ht.set_name("a_tag".to_string());
let tag = Tag::from_activity(conn, &ht, post_id, true)?;
assert_eq!(&tag.tag, "a_tag");
assert!(tag.is_hashtag);
Ok(())
});
}
#[test]
fn to_activity() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
fill_database(conn);
let tag = Tag {
id: 0,
tag: "a_tag".into(),
is_hashtag: false,
post_id: 0,
};
let act = tag.to_activity()?;
let expected = json!({
"href": "https://plu.me/tag/a_tag",
"name": "a_tag",
"type": "Hashtag"
});
assert_json_eq!(to_value(&act)?, expected);
Ok(())
})
}
#[test]
fn build_activity() {
let conn = &db();
conn.test_transaction::<_, Error, _>(|| {
fill_database(conn);
let act = Tag::build_activity("a_tag".into())?;
let expected = json!({
"href": "https://plu.me/tag/a_tag",
"name": "a_tag",
"type": "Hashtag"
});
assert_json_eq!(to_value(&act)?, expected);
Ok(())
});
}
}
+11
View File
@@ -6,6 +6,7 @@ use crate::{
Connection, Error, Result, Connection, Error, Result,
}; };
use diesel::{self, BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, BoolExpressionMethods, ExpressionMethods, QueryDsl, RunQueryDsl};
use std::cmp::Ordering;
use std::ops::Deref; use std::ops::Deref;
pub(crate) mod query; pub(crate) mod query;
@@ -92,6 +93,16 @@ impl Timeline {
.load::<Self>(conn) .load::<Self>(conn)
.map_err(Error::from) .map_err(Error::from)
} }
.map(|mut timelines| {
timelines.sort_by(|t1, t2| {
if t1.user_id.is_some() && t2.user_id.is_none() {
Ordering::Less
} else {
t1.id.cmp(&t2.id)
}
});
timelines
})
} }
pub fn new_for_user( pub fn new_for_user(
+420 -171
View File
@@ -4,12 +4,15 @@ use crate::{
safe_string::SafeString, schema::users, timeline::Timeline, Connection, Error, Result, safe_string::SafeString, schema::users, timeline::Timeline, Connection, Error, Result,
UserEvent::*, CONFIG, ITEMS_PER_PAGE, USER_CHAN, UserEvent::*, CONFIG, ITEMS_PER_PAGE, USER_CHAN,
}; };
use activitypub::{ use activitystreams::{
activity::Delete, activity::Delete,
actor::Person, actor::{ApActor, AsApActor, Endpoints, Person},
base::{AnyBase, Base},
collection::{OrderedCollection, OrderedCollectionPage}, collection::{OrderedCollection, OrderedCollectionPage},
object::{Image, Tombstone}, iri_string::types::IriString,
Activity, CustomObject, Endpoint, markers::Activity,
object::{kind::ImageType, AsObject as _, Image, Tombstone},
prelude::*,
}; };
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl}; use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
@@ -25,7 +28,8 @@ use plume_common::{
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
request::get, request::get,
sign::{gen_keypair, Error as SignError, Result as SignResult, Signer}, sign::{gen_keypair, Error as SignError, Result as SignResult, Signer},
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY, ActivityStream, ApSignature, CustomPerson, Id, IntoId, PublicKey, ToAsString, ToAsUri,
PUBLIC_VISIBILITY,
}, },
utils, utils,
}; };
@@ -39,11 +43,8 @@ use std::{
hash::{Hash, Hasher}, hash::{Hash, Hasher},
sync::Arc, sync::Arc,
}; };
use url::Url;
use webfinger::*; use webfinger::*;
pub type CustomPerson = CustomObject<ApSignature, Person>;
pub enum Role { pub enum Role {
Admin = 0, Admin = 0,
Moderator = 1, Moderator = 1,
@@ -202,6 +203,22 @@ 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
@@ -227,12 +244,22 @@ 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 res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
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)?;
let mut json = serde_json::from_str::<CustomPerson>(text)?; let person = serde_json::from_str::<Person>(text)?;
json.custom_props = ap_sign; let json = CustomPerson::new(
ApActor::new(
person
.clone()
.id_unchecked()
.ok_or(Error::MissingApProperty)?
.to_owned(),
person,
),
ap_sign,
); // FIXME: Don't clone()
Ok(json) Ok(json)
} }
@@ -244,35 +271,56 @@ impl User {
User::fetch(&self.ap_url.clone()).and_then(|json| { User::fetch(&self.ap_url.clone()).and_then(|json| {
let avatar = Media::save_remote( let avatar = Media::save_remote(
conn, conn,
json.object json.ap_actor_ref()
.object_props .icon()
.icon_image()? // FIXME: Fails when icon is not set .ok_or(Error::MissingApProperty)? // FIXME: Fails when icon is not set
.object_props .iter()
.url_string()?, .next()
.and_then(|i| {
i.clone()
.extend::<Image, ImageType>() // FIXME: Don't clone()
.ok()?
.and_then(|url| Some(url.id_unchecked()?.to_string()))
})
.ok_or(Error::MissingApProperty)?,
self, self,
) )
.ok(); .ok();
let pub_key = &json.ext_one.public_key.public_key_pem;
diesel::update(self) diesel::update(self)
.set(( .set((
users::username.eq(json.object.ap_actor_props.preferred_username_string()?), users::username.eq(json
users::display_name.eq(json.object.object_props.name_string()?), .ap_actor_ref()
users::outbox_url.eq(json.object.ap_actor_props.outbox_string()?), .preferred_username()
users::inbox_url.eq(json.object.ap_actor_props.inbox_string()?), .ok_or(Error::MissingApProperty)?),
users::display_name.eq(json
.ap_actor_ref()
.name()
.ok_or(Error::MissingApProperty)?
.to_as_string()
.ok_or(Error::MissingApProperty)?),
users::outbox_url.eq(json
.ap_actor_ref()
.outbox()?
.ok_or(Error::MissingApProperty)?
.as_str()),
users::inbox_url.eq(json.ap_actor_ref().inbox()?.as_str()),
users::summary.eq(SafeString::new( users::summary.eq(SafeString::new(
&json &json
.object .ap_actor_ref()
.object_props .summary()
.summary_string() .and_then(|summary| summary.to_as_string())
.unwrap_or_default(), .unwrap_or_default(),
)), )),
users::followers_endpoint.eq(json.object.ap_actor_props.followers_string()?), users::followers_endpoint.eq(json
.ap_actor_ref()
.followers()?
.ok_or(Error::MissingApProperty)?
.as_str()),
users::avatar_id.eq(avatar.map(|a| a.id)), users::avatar_id.eq(avatar.map(|a| a.id)),
users::last_fetched_date.eq(Utc::now().naive_utc()), users::last_fetched_date.eq(Utc::now().naive_utc()),
users::public_key.eq(json users::public_key.eq(pub_key),
.custom_props
.public_key_publickey()?
.public_key_pem_string()?),
)) ))
.execute(conn) .execute(conn)
.map(|_| ()) .map(|_| ())
@@ -413,48 +461,63 @@ impl User {
.map_err(Error::from) .map_err(Error::from)
} }
pub fn outbox(&self, conn: &Connection) -> Result<ActivityStream<OrderedCollection>> { pub fn outbox(&self, conn: &Connection) -> Result<ActivityStream<OrderedCollection>> {
let mut coll = OrderedCollection::default(); Ok(ActivityStream::new(self.outbox_collection(conn)?))
}
pub fn outbox_collection(&self, conn: &Connection) -> Result<OrderedCollection> {
let mut coll = OrderedCollection::new();
let first = &format!("{}?page=1", &self.outbox_url); let first = &format!("{}?page=1", &self.outbox_url);
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.set_first(first.parse::<IriString>()?);
coll.collection_props.set_last_link(Id::new(last))?; coll.set_last(last.parse::<IriString>()?);
coll.collection_props coll.set_total_items(self.get_activities_count(conn) as u64);
.set_total_items_u64(self.get_activities_count(conn) as u64)?; Ok(coll)
Ok(ActivityStream::new(coll))
} }
pub fn outbox_page( pub fn outbox_page(
&self, &self,
conn: &Connection, conn: &Connection,
(min, max): (i32, i32), (min, max): (i32, i32),
) -> Result<ActivityStream<OrderedCollectionPage>> { ) -> Result<ActivityStream<OrderedCollectionPage>> {
Ok(ActivityStream::new(
self.outbox_collection_page(conn, (min, max))?,
))
}
pub fn outbox_collection_page(
&self,
conn: &Connection,
(min, max): (i32, i32),
) -> Result<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::new();
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.set_next(
"{}?page={}", format!("{}?page={}", &self.outbox_url, min / ITEMS_PER_PAGE + 2)
&self.outbox_url, .parse::<IriString>()?,
min / ITEMS_PER_PAGE + 2 );
)))?;
} }
if min > 0 { if min > 0 {
coll.collection_page_props.set_prev_link(Id::new(&format!( coll.set_prev(
"{}?page={}", format!("{}?page={}", &self.outbox_url, min / ITEMS_PER_PAGE)
&self.outbox_url, .parse::<IriString>()?,
min / ITEMS_PER_PAGE );
)))?;
} }
coll.collection_props.items = serde_json::to_value(acts)?; coll.set_many_items(
coll.collection_page_props acts.iter()
.set_part_of_link(Id::new(&self.outbox_url))?; .filter_map(|value| AnyBase::from_arbitrary_json(value).ok()),
Ok(ActivityStream::new(coll)) );
coll.set_part_of(self.outbox_url.parse::<IriString>()?);
Ok(coll)
} }
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())?; pub fn fetch_outbox_page<T: Activity + serde::de::DeserializeOwned>(
&self,
url: &str,
) -> Result<(Vec<T>, Option<String>)> {
let res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
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"]
@@ -467,8 +530,9 @@ impl User {
let next = json.get("next").map(|x| x.as_str().unwrap().to_owned()); let next = json.get("next").map(|x| x.as_str().unwrap().to_owned());
Ok((items, next)) Ok((items, next))
} }
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
let mut res = get( pub fn fetch_outbox<T: Activity + serde::de::DeserializeOwned>(&self) -> Result<Vec<T>> {
let res = get(
&self.outbox_url[..], &self.outbox_url[..],
Self::get_sender(), Self::get_sender(),
CONFIG.proxy().cloned(), CONFIG.proxy().cloned(),
@@ -504,7 +568,7 @@ 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 res = get(
&self.followers_endpoint[..], &self.followers_endpoint[..],
Self::get_sender(), Self::get_sender(),
CONFIG.proxy().cloned(), CONFIG.proxy().cloned(),
@@ -712,71 +776,58 @@ impl User {
} }
pub fn to_activity(&self, conn: &Connection) -> Result<CustomPerson> { pub fn to_activity(&self, conn: &Connection) -> Result<CustomPerson> {
let mut actor = Person::default(); let mut actor = ApActor::new(self.inbox_url.parse()?, Person::new());
actor.object_props.set_id_string(self.ap_url.clone())?; let ap_url = self.ap_url.parse::<IriString>()?;
actor actor.set_id(ap_url.clone());
.object_props actor.set_name(self.display_name.clone());
.set_name_string(self.display_name.clone())?; actor.set_summary(self.summary_html.get().clone());
actor actor.set_url(ap_url.clone());
.object_props actor.set_inbox(self.inbox_url.parse()?);
.set_summary_string(self.summary_html.get().clone())?; actor.set_outbox(self.outbox_url.parse()?);
actor.object_props.set_url_string(self.ap_url.clone())?; actor.set_preferred_username(self.username.clone());
actor actor.set_followers(self.followers_endpoint.parse()?);
.ap_actor_props
.set_inbox_string(self.inbox_url.clone())?;
actor
.ap_actor_props
.set_outbox_string(self.outbox_url.clone())?;
actor
.ap_actor_props
.set_preferred_username_string(self.username.clone())?;
actor
.ap_actor_props
.set_followers_string(self.followers_endpoint.clone())?;
if let Some(shared_inbox_url) = self.shared_inbox_url.clone() { if let Some(shared_inbox_url) = self.shared_inbox_url.clone() {
let mut endpoints = Endpoint::default(); let endpoints = Endpoints {
endpoints.set_shared_inbox_string(shared_inbox_url)?; shared_inbox: Some(shared_inbox_url.parse::<IriString>()?),
actor.ap_actor_props.set_endpoints_endpoint(endpoints)?; ..Endpoints::default()
};
actor.set_endpoints(endpoints);
} }
let mut public_key = PublicKey::default(); let pub_key = PublicKey {
public_key.set_id_string(format!("{}#main-key", self.ap_url))?; id: format!("{}#main-key", self.ap_url).parse()?,
public_key.set_owner_string(self.ap_url.clone())?; owner: ap_url,
public_key.set_public_key_pem_string(self.public_key.clone())?; public_key_pem: self.public_key.clone(),
let mut ap_signature = ApSignature::default(); };
ap_signature.set_public_key_publickey(public_key)?; let ap_signature = ApSignature {
public_key: pub_key,
};
let mut avatar = Image::default(); if let Some(avatar_id) = self.avatar_id {
avatar.object_props.set_url_string( let mut avatar = Image::new();
self.avatar_id avatar.set_url(Media::get(conn, avatar_id)?.url()?.parse::<IriString>()?);
.and_then(|id| Media::get(conn, id).and_then(|m| m.url()).ok()) actor.set_icon(avatar.into_any_base()?);
.unwrap_or_default(), }
)?;
actor.object_props.set_icon_object(avatar)?;
Ok(CustomPerson::new(actor, ap_signature)) Ok(CustomPerson::new(actor, ap_signature))
} }
pub fn delete_activity(&self, conn: &Connection) -> Result<Delete> { pub fn delete_activity(&self, conn: &Connection) -> Result<Delete> {
let mut del = Delete::default(); let mut tombstone = Tombstone::new();
tombstone.set_id(self.ap_url.parse()?);
let mut tombstone = Tombstone::default(); let mut del = Delete::new(
tombstone.object_props.set_id_string(self.ap_url.clone())?; self.ap_url.parse::<IriString>()?,
Base::retract(tombstone)?.into_generic()?,
del.delete_props );
.set_actor_link(Id::new(self.ap_url.clone()))?; del.set_id(format!("{}#delete", self.ap_url).parse()?);
del.delete_props.set_object_object(tombstone)?; del.set_many_tos(vec![PUBLIC_VISIBILITY.parse::<IriString>()?]);
del.object_props del.set_many_ccs(
.set_id_string(format!("{}#delete", self.ap_url))?;
del.object_props
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY)])?;
del.object_props.set_cc_link_vec(
self.get_followers(conn)? self.get_followers(conn)?
.into_iter() .into_iter()
.map(|f| Id::new(f.ap_url)) .filter_map(|f| f.ap_url.parse::<IriString>().ok()),
.collect(), );
)?;
Ok(del) Ok(del)
} }
@@ -902,9 +953,60 @@ 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 actor = acct.ap_actor_ref();
let inst = url.host_str().ok_or(Error::Url)?; let username = actor
let instance = Instance::find_by_domain(conn, inst).or_else(|_| { .preferred_username()
.ok_or(Error::MissingApProperty)?
.to_string();
if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
return Err(Error::InvalidValue);
}
let summary = acct
.object_ref()
.summary()
.and_then(|prop| prop.to_as_string())
.unwrap_or_default();
let mut new_user = NewUser {
display_name: acct
.object_ref()
.name()
.and_then(|prop| prop.to_as_string())
.unwrap_or_else(|| username.clone()),
username: username.clone(),
outbox_url: actor.outbox()?.ok_or(Error::MissingApProperty)?.to_string(),
inbox_url: actor.inbox()?.to_string(),
role: 2,
summary_html: SafeString::new(&summary),
summary,
public_key: acct.ext_one.public_key.public_key_pem.to_string(),
shared_inbox_url: actor
.endpoints()?
.and_then(|e| e.shared_inbox.map(|inbox| inbox.to_string())),
followers_endpoint: actor
.followers()?
.ok_or(Error::MissingApProperty)?
.to_string(),
..NewUser::default()
};
let avatar_id = acct.object_ref().icon().and_then(|icon| icon.to_as_uri());
let (ap_url, inst) = {
let any_base = acct.into_any_base()?;
let id = any_base.id().ok_or(Error::MissingApProperty)?;
(
id.to_string(),
id.authority_components()
.ok_or(Error::Url)?
.host()
.to_string(),
)
};
new_user.ap_url = ap_url;
let instance = Instance::find_by_domain(conn, &inst).or_else(|_| {
Instance::insert( Instance::insert(
conn, conn,
NewInstance { NewInstance {
@@ -921,70 +1023,20 @@ impl FromId<DbConn> for User {
}, },
) )
})?; })?;
new_user.instance_id = instance.id;
let username = acct.object.ap_actor_props.preferred_username_string()?; new_user.fqn = if instance.local {
username
if username.contains(&['<', '>', '&', '@', '\'', '"', ' ', '\t'][..]) {
return Err(Error::InvalidValue);
}
let fqn = if instance.local {
username.clone()
} else { } else {
format!("{}@{}", username, instance.public_domain) format!("{}@{}", username, instance.public_domain)
}; };
let user = User::insert( let user = User::insert(conn, new_user)?;
conn, if let Some(avatar_id) = avatar_id {
NewUser { let avatar = Media::save_remote(conn, avatar_id, &user);
display_name: acct
.object
.object_props
.name_string()
.unwrap_or_else(|_| username.clone()),
username,
outbox_url: acct.object.ap_actor_props.outbox_string()?,
inbox_url: acct.object.ap_actor_props.inbox_string()?,
role: 2,
summary: acct
.object
.object_props
.summary_string()
.unwrap_or_default(),
summary_html: SafeString::new(
&acct
.object
.object_props
.summary_string()
.unwrap_or_default(),
),
email: None,
hashed_password: None,
instance_id: instance.id,
ap_url: acct.object.object_props.id_string()?,
public_key: acct
.custom_props
.public_key_publickey()?
.public_key_pem_string()?,
private_key: None,
shared_inbox_url: acct
.object
.ap_actor_props
.endpoints_endpoint()
.and_then(|e| e.shared_inbox_string())
.ok(),
followers_endpoint: acct.object.ap_actor_props.followers_string()?,
fqn,
avatar_id: None,
},
)?;
if let Ok(icon) = acct.object.object_props.icon_image() { if let Ok(avatar) = avatar {
if let Ok(url) = icon.object_props.url_string() { if let Err(e) = user.set_avatar(conn, avatar.id) {
let avatar = Media::save_remote(conn, url, &user); tracing::error!("{:?}", e);
if let Ok(avatar) = avatar {
user.set_avatar(conn, avatar.id)?;
} }
} }
} }
@@ -1126,10 +1178,13 @@ pub(crate) mod tests {
use super::*; use super::*;
use crate::{ use crate::{
instance::{tests as instance_tests, Instance}, instance::{tests as instance_tests, Instance},
medias::{Media, NewMedia},
tests::db, tests::db,
Connection as Conn, Connection as Conn, ITEMS_PER_PAGE,
}; };
use diesel::Connection; use assert_json_diff::assert_json_eq;
use diesel::{Connection, SaveChangesDsl};
use serde_json::to_value;
pub(crate) fn fill_database(conn: &Conn) -> Vec<User> { pub(crate) fn fill_database(conn: &Conn) -> Vec<User> {
instance_tests::fill_database(conn); instance_tests::fill_database(conn);
@@ -1153,7 +1208,7 @@ pub(crate) mod tests {
Some("invalid_user_password".to_owned()), Some("invalid_user_password".to_owned()),
) )
.unwrap(); .unwrap();
let other = NewUser::new_local( let mut other = NewUser::new_local(
conn, conn,
"other".to_owned(), "other".to_owned(),
"Another user".to_owned(), "Another user".to_owned(),
@@ -1163,9 +1218,73 @@ pub(crate) mod tests {
Some("invalid_other_password".to_owned()), Some("invalid_other_password".to_owned()),
) )
.unwrap(); .unwrap();
let avatar = Media::insert(
conn,
NewMedia {
file_path: "static/media/example.png".into(),
alt_text: "Another user".into(),
is_remote: false,
remote_url: None,
sensitive: false,
content_warning: None,
owner_id: other.id,
},
)
.unwrap();
other.avatar_id = Some(avatar.id);
let other = other.save_changes::<User>(&*conn).unwrap();
vec![admin, user, other] vec![admin, user, other]
} }
fn fill_pages(
conn: &DbConn,
) -> (
Vec<crate::posts::Post>,
Vec<crate::users::User>,
Vec<crate::blogs::Blog>,
) {
use crate::post_authors::NewPostAuthor;
use crate::posts::NewPost;
let (mut posts, users, blogs) = crate::inbox::tests::fill_database(conn);
let user = &users[0];
let blog = &blogs[0];
for i in 1..(ITEMS_PER_PAGE * 4 + 3) {
let title = format!("Post {}", i);
let content = format!("Content for post {}.", i);
let post = Post::insert(
conn,
NewPost {
blog_id: blog.id,
slug: title.clone(),
title: title.clone(),
content: SafeString::new(&content),
published: true,
license: "CC-0".into(),
creation_date: None,
ap_url: format!("{}/{}", blog.ap_url, title),
subtitle: "".into(),
source: content,
cover_id: None,
},
)
.unwrap();
PostAuthor::insert(
conn,
NewPostAuthor {
post_id: post.id,
author_id: user.id,
},
)
.unwrap();
posts.push(post);
}
(posts, users, blogs)
}
#[test] #[test]
fn find_by() { fn find_by() {
let conn = db(); let conn = db();
@@ -1326,4 +1445,134 @@ pub(crate) mod tests {
Ok(()) Ok(())
}); });
} }
#[test]
fn to_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let users = fill_database(&conn);
let user = &users[0];
let act = user.to_activity(&conn)?;
let expected = json!({
"endpoints": {
"sharedInbox": "https://plu.me/inbox"
},
"followers": "https://plu.me/@/admin/followers",
"id": "https://plu.me/@/admin/",
"inbox": "https://plu.me/@/admin/inbox",
"name": "The admin",
"outbox": "https://plu.me/@/admin/outbox",
"preferredUsername": "admin",
"publicKey": {
"id": "https://plu.me/@/admin/#main-key",
"owner": "https://plu.me/@/admin/",
"publicKeyPem": user.public_key,
},
"summary": "<p dir=\"auto\">Hello there, Im the admin</p>\n",
"type": "Person",
"url": "https://plu.me/@/admin/"
});
assert_json_eq!(to_value(act)?, expected);
let other = &users[2];
let other_act = other.to_activity(&conn)?;
let expected_other = json!({
"endpoints": {
"sharedInbox": "https://plu.me/inbox"
},
"followers": "https://plu.me/@/other/followers",
"icon": {
"url": "https://plu.me/static/media/example.png",
"type": "Image",
},
"id": "https://plu.me/@/other/",
"inbox": "https://plu.me/@/other/inbox",
"name": "Another user",
"outbox": "https://plu.me/@/other/outbox",
"preferredUsername": "other",
"publicKey": {
"id": "https://plu.me/@/other/#main-key",
"owner": "https://plu.me/@/other/",
"publicKeyPem": other.public_key,
},
"summary": "<p dir=\"auto\">Hello there, Im someone else</p>\n",
"type": "Person",
"url": "https://plu.me/@/other/"
});
assert_json_eq!(to_value(other_act)?, expected_other);
Ok(())
});
}
#[test]
fn delete_activity() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let users = fill_database(&conn);
let user = &users[1];
let act = user.delete_activity(&conn)?;
let expected = json!({
"actor": "https://plu.me/@/user/",
"cc": [],
"id": "https://plu.me/@/user/#delete",
"object": {
"id": "https://plu.me/@/user/",
"type": "Tombstone",
},
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"type": "Delete",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn outbox_collection() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let (_pages, users, _blogs) = fill_pages(&conn);
let user = &users[0];
let act = user.outbox_collection(&conn)?;
let expected = json!({
"first": "https://plu.me/@/admin/outbox?page=1",
"last": "https://plu.me/@/admin/outbox?page=5",
"totalItems": 51,
"type": "OrderedCollection",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
#[test]
fn outbox_collection_page() {
let conn = db();
conn.test_transaction::<_, Error, _>(|| {
let users = fill_database(&conn);
let user = &users[0];
let act = user.outbox_collection_page(&conn, (33, 36))?;
let expected = json!({
"items": [],
"partOf": "https://plu.me/@/admin/outbox",
"prev": "https://plu.me/@/admin/outbox?page=2",
"type": "OrderedCollectionPage",
});
assert_json_eq!(to_value(act)?, expected);
Ok(())
});
}
} }
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "فتح محرر النصوص الغني" msgstr "فتح محرر النصوص الغني"
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "العنوان" msgstr "العنوان"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "العنوان الثانوي أو الملخص" msgstr "العنوان الثانوي أو الملخص"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "اكتب مقالك هنا. ماركداون مُدَعَّم." msgstr "اكتب مقالك هنا. ماركداون مُدَعَّم."
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "يتبقا {} حرفا تقريبا" msgstr "يتبقا {} حرفا تقريبا"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "الوسوم" msgstr "الوسوم"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "الرخصة" msgstr "الرخصة"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "الغلاف" msgstr "الغلاف"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "هذه مسودة" msgstr "هذه مسودة"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "نشر كتابا" msgstr "نشر كتابا"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Отворете редактора с богат текст" msgstr "Отворете редактора с богат текст"
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Заглавие" msgstr "Заглавие"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Подзаглавие или резюме" msgstr "Подзаглавие или резюме"
# plume-front/src/editor.rs:325 # 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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Остават {} знака вляво" msgstr "Остават {} знака вляво"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Етикети" msgstr "Етикети"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Лиценз" msgstr "Лиценз"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Основно изображение" msgstr "Основно изображение"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Това е проект" msgstr "Това е проект"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Публикувай" msgstr "Публикувай"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Obre leditor de text enriquit" msgstr "Obre leditor de text enriquit"
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Títol" msgstr "Títol"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Subtítol o resum" msgstr "Subtítol o resum"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Queden uns {} caràcters" msgstr "Queden uns {} caràcters"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etiquetes" msgstr "Etiquetes"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Llicència" msgstr "Llicència"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Coberta" msgstr "Coberta"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
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:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publica" msgstr "Publica"
+12 -12
View File
@@ -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-02 11:39\n" "PO-Revision-Date: 2022-05-09 09:58\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Nadpis" msgstr "Nadpis"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Podnadpis, nebo shrnutí" msgstr "Podnadpis, nebo shrnutí"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Zbývá kolem {} znaků" msgstr "Zbývá kolem {} znaků"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Tagy" msgstr "Tagy"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licence" msgstr "Licence"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Titulka" msgstr "Titulka"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Tohle je koncept" msgstr "Tohle je koncept"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Zveřejnit" msgstr "Zveřejnit"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-12-11 15:00\n" "PO-Revision-Date: 2022-01-26 13:16\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:188 # plume-front/src/editor.rs:172
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 "Möchten Sie die lokale automatische Speicherung laden, die zuletzt um {} bearbeitet wurde?"
# plume-front/src/editor.rs:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Untertitel oder Zusammenfassung" msgstr "Untertitel oder Zusammenfassung"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Ungefähr {} Zeichen übrig" msgstr "Ungefähr {} Zeichen übrig"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Schlagwörter" msgstr "Schlagwörter"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Lizenz" msgstr "Lizenz"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Einband" msgstr "Einband"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
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:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Veröffentlichen" msgstr "Veröffentlichen"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titolo" msgstr "Titolo"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Proksimume {} signoj restantaj" msgstr "Proksimume {} signoj restantaj"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etikedoj" msgstr "Etikedoj"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Permesilo" msgstr "Permesilo"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Kovro" msgstr "Kovro"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Malfinias" msgstr "Malfinias"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Eldoni" msgstr "Eldoni"
+12 -12
View File
@@ -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-02 11:39\n" "PO-Revision-Date: 2022-01-26 13:16\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:188 # plume-front/src/editor.rs:172
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 "¿Quieres cargar el guardado automático local editado por última vez en {}?"
# plume-front/src/editor.rs:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Título" msgstr "Título"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Subtítulo, o resumen" msgstr "Subtítulo, o resumen"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Quedan unos {} caracteres" msgstr "Quedan unos {} caracteres"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etiquetas" msgstr "Etiquetas"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licencia" msgstr "Licencia"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Cubierta" msgstr "Cubierta"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
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:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publicar" msgstr "Publicar"
+63
View File
@@ -0,0 +1,63 @@
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-05-09 09:58\n"
"Last-Translator: \n"
"Language-Team: Basque\n"
"Language: eu_ES\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: eu\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 "{}(t)an automatikoki gordetako azken kopia lokala kargatu nahi al duzu?"
# plume-front/src/editor.rs:326
msgid "Open the rich text editor"
msgstr "Ireki testu-formatutzaile aberatsa"
# plume-front/src/editor.rs:385
msgid "Title"
msgstr "Izenburua"
# plume-front/src/editor.rs:389
msgid "Subtitle, or summary"
msgstr "Azpititulua edo laburpena"
# plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported."
msgstr "Idatzi hemen testua. Markdown erabil dezakezu."
# plume-front/src/editor.rs:407
msgid "Around {} characters left"
msgstr "%{count} karaktere geratzen dira"
# plume-front/src/editor.rs:517
msgid "Tags"
msgstr "Etiketak"
# plume-front/src/editor.rs:518
msgid "License"
msgstr "Lizentzia"
# plume-front/src/editor.rs:524
msgid "Cover"
msgstr "Azala"
# plume-front/src/editor.rs:564
msgid "This is a draft"
msgstr "Zirriborro bat da"
# plume-front/src/editor.rs:575
msgid "Publish"
msgstr "Argitaratu"
+13 -13
View File
@@ -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: 2021-12-11 15:00\n" "PO-Revision-Date: 2022-05-10 17:54\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "باز کردن ویرایشگر غنی" msgstr "گشودن ویرایشگر غنی"
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "عنوان" msgstr "عنوان"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "زیرعنوان، یا چکیده" msgstr "زیرعنوان، یا چکیده"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "مقاله‌تان را این‌جا بنویسید. از مارک‌داون پشتیبانی می‌شود." msgstr "مقاله‌تان را این‌جا بنویسید. از مارک‌داون پشتیبانی می‌شود."
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "نزدیک به {} حرف باقی مانده است" msgstr "نزدیک به {} حرف باقی مانده است"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "برچسب‌ها" msgstr "برچسب‌ها"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "پروانه" msgstr "پروانه"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "جلد" msgstr "جلد"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "این، یک پیش‌نویس است" msgstr "این، یک پیش‌نویس است"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "انتشار" msgstr "انتشار"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Otsikko" msgstr "Otsikko"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Alaotsikko tai tiivistelmä" msgstr "Alaotsikko tai tiivistelmä"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "%{count} merkkiä jäljellä" msgstr "%{count} merkkiä jäljellä"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Tagit" msgstr "Tagit"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Lisenssi" msgstr "Lisenssi"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Kansi" msgstr "Kansi"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
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:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Julkaise" msgstr "Julkaise"
+12 -12
View File
@@ -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: 2021-12-11 15:00\n" "PO-Revision-Date: 2022-05-09 09:59\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:188 # plume-front/src/editor.rs:172
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 "Voulez vous charger la sauvegarde automatique locale, éditée la dernière fois à {}?"
# plume-front/src/editor.rs:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
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:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Environ {} caractères restant" msgstr "Environ {} caractères restant"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Étiquettes" msgstr "Étiquettes"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licence" msgstr "Licence"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Illustration" msgstr "Illustration"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
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:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publier" msgstr "Publier"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-26 13:16\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:188 # plume-front/src/editor.rs:172
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 "Queres cargar a última copia gardada editada o {}?"
# plume-front/src/editor.rs:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Título" msgstr "Título"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Subtítulo, ou resumo" msgstr "Subtítulo, ou resumo"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Dispós de {} caracteres" msgstr "Dispós de {} caracteres"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etiquetas" msgstr "Etiquetas"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licenza" msgstr "Licenza"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Portada" msgstr "Portada"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Este é un borrador" msgstr "Este é un borrador"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publicar" msgstr "Publicar"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "शीर्षक" msgstr "शीर्षक"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # 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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "लगभग {} अक्षर बाकी हैं" msgstr "लगभग {} अक्षर बाकी हैं"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "टैग्स" msgstr "टैग्स"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "लाइसेंस" msgstr "लाइसेंस"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "पब्लिश करें" msgstr "पब्लिश करें"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Naslov" msgstr "Naslov"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Tagovi" msgstr "Tagovi"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licenca" msgstr "Licenca"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Objavi" msgstr "Objavi"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titolo" msgstr "Titolo"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Sottotitolo, o sommario" msgstr "Sottotitolo, o sommario"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Circa {} caratteri rimasti" msgstr "Circa {} caratteri rimasti"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etichette" msgstr "Etichette"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licenza" msgstr "Licenza"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Copertina" msgstr "Copertina"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Questa è una bozza" msgstr "Questa è una bozza"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Pubblica" msgstr "Pubblica"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "リッチテキストエディターを開く" msgstr "リッチテキストエディターを開く"
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "タイトル" msgstr "タイトル"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "サブタイトル、または概要" msgstr "サブタイトル、または概要"
# plume-front/src/editor.rs:325 # 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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "残り約 {} 文字" msgstr "残り約 {} 文字"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "タグ" msgstr "タグ"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "ライセンス" msgstr "ライセンス"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "カバー" msgstr "カバー"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "これは下書きです" msgstr "これは下書きです"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "公開" msgstr "公開"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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 "Wilt u de lokale auto-opslaan laden? Laatst bewerkt om: {}"
# plume-front/src/editor.rs:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Ondertitel of samenvatting" msgstr "Ondertitel of samenvatting"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Ongeveer {} tekens over" msgstr "Ongeveer {} tekens over"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Tags" msgstr "Tags"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licentie" msgstr "Licentie"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Hoofdafbeelding" msgstr "Hoofdafbeelding"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
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:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publiceren" msgstr "Publiceren"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Lisens" msgstr "Lisens"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publiser" msgstr "Publiser"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Tytuł" msgstr "Tytuł"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Podtytuł, lub podsumowanie" msgstr "Podtytuł, lub podsumowanie"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
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:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Tagi" msgstr "Tagi"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licencja" msgstr "Licencja"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Okładka" msgstr "Okładka"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "To jest szkic" msgstr "To jest szkic"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publikuj" msgstr "Publikuj"
-11
View File
@@ -12,46 +12,35 @@ 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:177
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:331
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:388
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:392
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:399
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:410
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:519
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:520
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:526
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:560
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:571
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-12-11 15:00\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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 "Você quer carregar o último conteúdo salvo localmente editado em {}?"
# plume-front/src/editor.rs:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Título" msgstr "Título"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Subtítulo ou resumo" msgstr "Subtítulo ou resumo"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Cerca de {} caracteres restantes" msgstr "Cerca de {} caracteres restantes"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Tags" msgstr "Tags"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licença" msgstr "Licença"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Capa" msgstr "Capa"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Isso é um rascunho" msgstr "Isso é um rascunho"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publicar" msgstr "Publicar"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titlu" msgstr "Titlu"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
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:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etichete" msgstr "Etichete"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licenţă" msgstr "Licenţă"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Coperta" msgstr "Coperta"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
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:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publică" msgstr "Publică"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-05-09 09:59\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "Открыть в визуальном редакторе" msgstr "Открыть в визуальном редакторе"
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Заголовок" msgstr "Заголовок"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Подзаголовок или резюме" msgstr "Подзаголовок или резюме"
# plume-front/src/editor.rs:325 # 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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Осталось около {} символов" msgstr "Осталось около {} символов"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Теги" msgstr "Теги"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Лицензия" msgstr "Лицензия"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Обложка" msgstr "Обложка"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Это черновик" msgstr "Это черновик"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Опубликовать" msgstr "Опубликовать"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "ᱨᱤᱪ ᱚᱞ ᱥᱟᱯᱟᱣᱤᱡ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ" msgstr "ᱨᱤᱪ ᱚᱞ ᱥᱟᱯᱟᱣᱤᱡ ᱠᱷᱩᱞᱟᱹᱭ ᱢᱮ"
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "ᱴᱭᱴᱚᱞ" msgstr "ᱴᱭᱴᱚᱞ"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "ᱥᱟᱹᱵᱴᱟᱭᱴᱟᱹᱞ, ᱟᱨ ᱵᱟᱝ ᱥᱟᱹᱢᱢᱟᱨᱭ" msgstr "ᱥᱟᱹᱵᱴᱟᱭᱴᱟᱹᱞ, ᱟᱨ ᱵᱟᱝ ᱥᱟᱹᱢᱢᱟᱨᱭ"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "ᱟᱢᱟᱜ ᱚᱱᱚᱞ ᱱᱚᱰᱮ ᱚᱞ ᱛᱟᱢ ᱾ ᱪᱤᱱᱦᱟᱹ ᱥᱟᱯᱚᱴ ᱜᱮᱭᱟ ᱾" msgstr "ᱟᱢᱟᱜ ᱚᱱᱚᱞ ᱱᱚᱰᱮ ᱚᱞ ᱛᱟᱢ ᱾ ᱪᱤᱱᱦᱟᱹ ᱥᱟᱯᱚᱴ ᱜᱮᱭᱟ ᱾"
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "ᱡᱷᱚᱛᱚ ᱨᱮ {} ᱡᱤᱱᱤᱥ ᱵᱟᱧᱪᱟᱣᱠᱟᱱᱟ" msgstr "ᱡᱷᱚᱛᱚ ᱨᱮ {} ᱡᱤᱱᱤᱥ ᱵᱟᱧᱪᱟᱣᱠᱟᱱᱟ"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "ᱴᱮᱜᱽᱥ" msgstr "ᱴᱮᱜᱽᱥ"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "ᱞᱚᱭᱥᱮᱱᱥ" msgstr "ᱞᱚᱭᱥᱮᱱᱥ"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "ᱢᱚᱭᱞᱟᱹᱴ" msgstr "ᱢᱚᱭᱞᱟᱹᱴ"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "ᱱᱚᱶᱟ ᱫᱚ ᱰᱨᱟᱯᱷᱼᱴ ᱠᱟᱱᱟ" msgstr "ᱱᱚᱶᱟ ᱫᱚ ᱰᱨᱟᱯᱷᱼᱴ ᱠᱟᱱᱟ"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "ᱯᱟᱨᱥᱟᱞ" msgstr "ᱯᱟᱨᱥᱟᱞ"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "මාතෘකාව" msgstr "මාතෘකාව"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "බලපත්‍රය" msgstr "බලපත්‍රය"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-12-11 15:00\n" "PO-Revision-Date: 2022-05-09 09:59\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:188 # plume-front/src/editor.rs:172
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 "Chceš načítať automaticky uloženú zálohu, s poslednou úpravou {}?"
# plume-front/src/editor.rs:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Nadpis" msgstr "Nadpis"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Zhrnutie, alebo podnadpis" msgstr "Zhrnutie, alebo podnadpis"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Zostáva asi {} znakov" msgstr "Zostáva asi {} znakov"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Štítky" msgstr "Štítky"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licencia" msgstr "Licencia"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Obálka" msgstr "Obálka"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Toto je koncept" msgstr "Toto je koncept"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Zverejniť" msgstr "Zverejniť"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Naslov" msgstr "Naslov"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Oznake" msgstr "Oznake"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licenca" msgstr "Licenca"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "To je osnutek" msgstr "To je osnutek"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Objavi" msgstr "Objavi"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Naslov" msgstr "Naslov"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "Podnaslov, ili sažetak" msgstr "Podnaslov, ili sažetak"
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Preostalo oko {} znakova" msgstr "Preostalo oko {} znakova"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Markeri" msgstr "Markeri"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licenca" msgstr "Licenca"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Naslovna strana" msgstr "Naslovna strana"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "Ovo je nacrt" msgstr "Ovo je nacrt"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Objavi" msgstr "Objavi"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
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:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Taggar" msgstr "Taggar"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Licens" msgstr "Licens"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Omslag" msgstr "Omslag"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Publicera" msgstr "Publicera"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Başlık" msgstr "Başlık"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
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:325 # plume-front/src/editor.rs:396
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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "Yaklaşık {} karakter kaldı" msgstr "Yaklaşık {} karakter kaldı"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "Etiketler" msgstr "Etiketler"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "Lisans" msgstr "Lisans"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "Kapak" msgstr "Kapak"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
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:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "Yayınla" msgstr "Yayınla"
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Urdu (Pakistan)\n" "Language-Team: Urdu (Pakistan)\n"
"Language: ur_PK\n" "Language: ur_PK\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+12 -12
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:20\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
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:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "Tiêu Châu" msgstr "Tiêu Châu"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:325 # plume-front/src/editor.rs:396
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""
+13 -13
View File
@@ -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: 2021-12-11 15:00\n" "PO-Revision-Date: 2022-05-09 09:58\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Chinese Traditional\n" "Language-Team: Chinese Traditional\n"
"Language: zh_TW\n" "Language: zh_TW\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:188 # plume-front/src/editor.rs:172
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:281 # plume-front/src/editor.rs:326
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "開啟 RTF 編輯器" msgstr "開啟 RTF 編輯器"
# plume-front/src/editor.rs:314 # plume-front/src/editor.rs:385
msgid "Title" msgid "Title"
msgstr "標題" msgstr "標題"
# plume-front/src/editor.rs:318 # plume-front/src/editor.rs:389
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "副標題,或摘要" msgstr "副標題,或摘要"
# plume-front/src/editor.rs:325 # 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:336 # plume-front/src/editor.rs:407
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "大約還可輸入 {} 字符" msgstr "大約還可輸入 {} 字符"
# plume-front/src/editor.rs:413 # plume-front/src/editor.rs:517
msgid "Tags" msgid "Tags"
msgstr "標籤" msgstr "標籤"
# plume-front/src/editor.rs:414 # plume-front/src/editor.rs:518
msgid "License" msgid "License"
msgstr "授權條款" msgstr "授權條款"
# plume-front/src/editor.rs:417 # plume-front/src/editor.rs:524
msgid "Cover" msgid "Cover"
msgstr "封面" msgstr "封面"
# plume-front/src/editor.rs:437 # plume-front/src/editor.rs:564
msgid "This is a draft" msgid "This is a draft"
msgstr "這是草稿" msgstr "這是草稿"
# plume-front/src/editor.rs:444 # plume-front/src/editor.rs:575
msgid "Publish" msgid "Publish"
msgstr "發" msgstr "發"
+98 -77
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:28\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Afrikaans\n" "Language-Team: Afrikaans\n"
"Language: af_ZA\n" "Language: af_ZA\n"
@@ -18,86 +18,90 @@ msgstr ""
"X-Crowdin-File-ID: 8\n" "X-Crowdin-File-ID: 8\n"
# src/template_utils.rs:105 # src/template_utils.rs:105
msgid "{0} commented on your article." msgid "Someone"
msgstr ""
# src/template_utils.rs:106
msgid "{0} is subscribed to you."
msgstr "" msgstr ""
# src/template_utils.rs:107 # src/template_utils.rs:107
msgid "{0} liked your article." msgid "{0} commented on your article."
msgstr "" msgstr ""
# src/template_utils.rs:108 # src/template_utils.rs:108
msgid "{0} mentioned you." msgid "{0} is subscribed to you."
msgstr "" msgstr ""
# src/template_utils.rs:109 # src/template_utils.rs:109
msgid "{0} liked your article."
msgstr ""
# src/template_utils.rs:110
msgid "{0} mentioned you."
msgstr ""
# src/template_utils.rs:111
msgid "{0} boosted your article." msgid "{0} boosted your article."
msgstr "" msgstr ""
# src/template_utils.rs:116 # src/template_utils.rs:118
msgid "Your feed" msgid "Your feed"
msgstr "" msgstr ""
# src/template_utils.rs:117 # src/template_utils.rs:119
msgid "Local feed" msgid "Local feed"
msgstr "" msgstr ""
# src/template_utils.rs:118 # src/template_utils.rs:120
msgid "Federated feed" msgid "Federated feed"
msgstr "" msgstr ""
# src/template_utils.rs:154 # src/template_utils.rs:156
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "" msgstr ""
# src/template_utils.rs:198 # src/template_utils.rs:200
msgid "Previous page" msgid "Previous page"
msgstr "" msgstr ""
# src/template_utils.rs:209 # src/template_utils.rs:211
msgid "Next page" msgid "Next page"
msgstr "" msgstr ""
# src/template_utils.rs:363 # src/template_utils.rs:365
msgid "Optional" msgid "Optional"
msgstr "" msgstr ""
# src/routes/blogs.rs:67 # src/routes/blogs.rs:68
msgid "To create a new blog, you need to be logged in" msgid "To create a new blog, you need to be logged in"
msgstr "" msgstr ""
# src/routes/blogs.rs:109 # src/routes/blogs.rs:110
msgid "A blog with the same name already exists." msgid "A blog with the same name already exists."
msgstr "" msgstr ""
# src/routes/blogs.rs:147 # src/routes/blogs.rs:148
msgid "Your blog was successfully created!" msgid "Your blog was successfully created!"
msgstr "" msgstr ""
# src/routes/blogs.rs:165 # src/routes/blogs.rs:166
msgid "Your blog was deleted." msgid "Your blog was deleted."
msgstr "" msgstr ""
# src/routes/blogs.rs:173 # src/routes/blogs.rs:174
msgid "You are not allowed to delete this blog." msgid "You are not allowed to delete this blog."
msgstr "" msgstr ""
# src/routes/blogs.rs:223 # src/routes/blogs.rs:224
msgid "You are not allowed to edit this blog." msgid "You are not allowed to edit this blog."
msgstr "" msgstr ""
# src/routes/blogs.rs:279 # src/routes/blogs.rs:280
msgid "You can't use this media as a blog icon." msgid "You can't use this media as a blog icon."
msgstr "" msgstr ""
# src/routes/blogs.rs:297 # src/routes/blogs.rs:298
msgid "You can't use this media as a blog banner." msgid "You can't use this media as a blog banner."
msgstr "" msgstr ""
# src/routes/blogs.rs:331 # src/routes/blogs.rs:332
msgid "Your blog information have been updated." msgid "Your blog information have been updated."
msgstr "" msgstr ""
@@ -109,6 +113,22 @@ msgstr ""
msgid "Your comment has been deleted." msgid "Your comment has been deleted."
msgstr "" msgstr ""
# src/routes/email_signups.rs:82
msgid "Registrations are closed on this instance."
msgstr ""
# src/routes/email_signups.rs:119
msgid "User registration"
msgstr ""
# src/routes/email_signups.rs:120
msgid "Here is the link for registration: {0}"
msgstr ""
# src/routes/email_signups.rs:219
msgid "Your account has been created. Now you just need to log in, before you can use it."
msgstr ""
# src/routes/instance.rs:117 # src/routes/instance.rs:117
msgid "Instance settings have been saved." msgid "Instance settings have been saved."
msgstr "" msgstr ""
@@ -149,19 +169,19 @@ msgstr ""
msgid "To like a post, you need to be logged in" msgid "To like a post, you need to be logged in"
msgstr "" msgstr ""
# src/routes/medias.rs:158 # src/routes/medias.rs:153
msgid "Your media have been deleted." msgid "Your media have been deleted."
msgstr "" msgstr ""
# src/routes/medias.rs:163 # src/routes/medias.rs:158
msgid "You are not allowed to delete this media." msgid "You are not allowed to delete this media."
msgstr "" msgstr ""
# src/routes/medias.rs:180 # src/routes/medias.rs:175
msgid "Your avatar has been updated." msgid "Your avatar has been updated."
msgstr "" msgstr ""
# src/routes/medias.rs:185 # src/routes/medias.rs:180
msgid "You are not allowed to use this media." msgid "You are not allowed to use this media."
msgstr "" msgstr ""
@@ -169,55 +189,55 @@ msgstr ""
msgid "To see your notifications, you need to be logged in" msgid "To see your notifications, you need to be logged in"
msgstr "" msgstr ""
# src/routes/posts.rs:55 # src/routes/posts.rs:56
msgid "This post isn't published yet." msgid "This post isn't published yet."
msgstr "" msgstr ""
# src/routes/posts.rs:125 # src/routes/posts.rs:126
msgid "To write a new post, you need to be logged in" msgid "To write a new post, you need to be logged in"
msgstr "" msgstr ""
# src/routes/posts.rs:146 # src/routes/posts.rs:147
msgid "You are not an author of this blog." msgid "You are not an author of this blog."
msgstr "" msgstr ""
# src/routes/posts.rs:153 # src/routes/posts.rs:154
msgid "New post" msgid "New post"
msgstr "" msgstr ""
# src/routes/posts.rs:198 # src/routes/posts.rs:199
msgid "Edit {0}" msgid "Edit {0}"
msgstr "" msgstr ""
# src/routes/posts.rs:267 # src/routes/posts.rs:268
msgid "You are not allowed to publish on this blog." msgid "You are not allowed to publish on this blog."
msgstr "" msgstr ""
# src/routes/posts.rs:367 # src/routes/posts.rs:368
msgid "Your article has been updated." msgid "Your article has been updated."
msgstr "" msgstr ""
# src/routes/posts.rs:556 # src/routes/posts.rs:557
msgid "Your article has been saved." msgid "Your article has been saved."
msgstr "" msgstr ""
# src/routes/posts.rs:563 # src/routes/posts.rs:564
msgid "New article" msgid "New article"
msgstr "" msgstr ""
# src/routes/posts.rs:601 # src/routes/posts.rs:602
msgid "You are not allowed to delete this article." msgid "You are not allowed to delete this article."
msgstr "" msgstr ""
# src/routes/posts.rs:625 # src/routes/posts.rs:626
msgid "Your article has been deleted." msgid "Your article has been deleted."
msgstr "" msgstr ""
# src/routes/posts.rs:630 # src/routes/posts.rs:631
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?" msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
msgstr "" msgstr ""
# src/routes/posts.rs:672 # src/routes/posts.rs:673
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct." msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
msgstr "" msgstr ""
@@ -245,44 +265,69 @@ msgstr ""
msgid "Your password was successfully reset." msgid "Your password was successfully reset."
msgstr "" msgstr ""
# src/routes/user.rs:74 # src/routes/user.rs:87
msgid "To access your dashboard, you need to be logged in" msgid "To access your dashboard, you need to be logged in"
msgstr "" msgstr ""
# src/routes/user.rs:96 # src/routes/user.rs:109
msgid "You are no longer following {}." msgid "You are no longer following {}."
msgstr "" msgstr ""
# src/routes/user.rs:113 # src/routes/user.rs:126
msgid "You are now following {}." msgid "You are now following {}."
msgstr "" msgstr ""
# src/routes/user.rs:190 # src/routes/user.rs:203
msgid "To subscribe to someone, you need to be logged in" msgid "To subscribe to someone, you need to be logged in"
msgstr "" msgstr ""
# src/routes/user.rs:299 # src/routes/user.rs:323
msgid "To edit your profile, you need to be logged in" msgid "To edit your profile, you need to be logged in"
msgstr "" msgstr ""
# src/routes/user.rs:345 # src/routes/user.rs:369
msgid "Your profile has been updated." msgid "Your profile has been updated."
msgstr "" msgstr ""
# src/routes/user.rs:373 # src/routes/user.rs:397
msgid "Your account has been deleted." msgid "Your account has been deleted."
msgstr "" msgstr ""
# src/routes/user.rs:379 # src/routes/user.rs:403
msgid "You can't delete someone else's account." msgid "You can't delete someone else's account."
msgstr "" msgstr ""
# src/routes/user.rs:463 msgid "Create your account"
msgid "Registrations are closed on this instance."
msgstr "" msgstr ""
# src/routes/user.rs:486 msgid "Create an account"
msgid "Your account has been created. Now you just need to log in, before you can use it." msgstr ""
msgid "Email"
msgstr ""
msgid "Email confirmation"
msgstr ""
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr ""
msgid "Registration"
msgstr ""
msgid "Check your inbox!"
msgstr ""
msgid "We sent a mail to the address you gave us, with a link for registration."
msgstr ""
msgid "Username"
msgstr ""
msgid "Password"
msgstr ""
msgid "Password confirmation"
msgstr "" msgstr ""
msgid "Media upload" msgid "Media upload"
@@ -423,27 +468,6 @@ msgstr ""
msgid "Subscriptions" msgid "Subscriptions"
msgstr "" msgstr ""
msgid "Create your account"
msgstr ""
msgid "Create an account"
msgstr ""
msgid "Username"
msgstr ""
msgid "Email"
msgstr ""
msgid "Password"
msgstr ""
msgid "Password confirmation"
msgstr ""
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr ""
msgid "{0}'s subscriptions" msgid "{0}'s subscriptions"
msgstr "" msgstr ""
@@ -841,9 +865,6 @@ msgstr ""
msgid "Update password" msgid "Update password"
msgstr "" msgstr ""
msgid "Check your inbox!"
msgstr ""
msgid "We sent a mail to the address you gave us, with a link to reset your password." msgid "We sent a mail to the address you gave us, with a link to reset your password."
msgstr "" msgstr ""
+97 -76
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:28\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Arabic\n" "Language-Team: Arabic\n"
"Language: ar_SA\n" "Language: ar_SA\n"
@@ -18,86 +18,90 @@ msgstr ""
"X-Crowdin-File-ID: 8\n" "X-Crowdin-File-ID: 8\n"
# src/template_utils.rs:105 # src/template_utils.rs:105
msgid "Someone"
msgstr ""
# src/template_utils.rs:107
msgid "{0} commented on your article." msgid "{0} commented on your article."
msgstr "علّق {0} على مقالك." msgstr "علّق {0} على مقالك."
# src/template_utils.rs:106 # src/template_utils.rs:108
msgid "{0} is subscribed to you." msgid "{0} is subscribed to you."
msgstr "{0} مشترك لك." msgstr "{0} مشترك لك."
# src/template_utils.rs:107 # src/template_utils.rs:109
msgid "{0} liked your article." msgid "{0} liked your article."
msgstr "{0} أعجبهم مقالك." msgstr "{0} أعجبهم مقالك."
# src/template_utils.rs:108 # src/template_utils.rs:110
msgid "{0} mentioned you." msgid "{0} mentioned you."
msgstr "أشار إليك {0}." msgstr "أشار إليك {0}."
# src/template_utils.rs:109 # src/template_utils.rs:111
msgid "{0} boosted your article." msgid "{0} boosted your article."
msgstr "{0} دعمو مقالك." msgstr "{0} دعمو مقالك."
# src/template_utils.rs:116 # src/template_utils.rs:118
msgid "Your feed" msgid "Your feed"
msgstr "خيطك" msgstr "خيطك"
# src/template_utils.rs:117 # src/template_utils.rs:119
msgid "Local feed" msgid "Local feed"
msgstr "الخيط المحلي" msgstr "الخيط المحلي"
# src/template_utils.rs:118 # src/template_utils.rs:120
msgid "Federated feed" msgid "Federated feed"
msgstr "الخيط الموحد" msgstr "الخيط الموحد"
# src/template_utils.rs:154 # src/template_utils.rs:156
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "الصورة الرمزية لـ {0}" msgstr "الصورة الرمزية لـ {0}"
# src/template_utils.rs:198 # src/template_utils.rs:200
msgid "Previous page" msgid "Previous page"
msgstr "الصفحة السابقة" msgstr "الصفحة السابقة"
# src/template_utils.rs:209 # src/template_utils.rs:211
msgid "Next page" msgid "Next page"
msgstr "الصفحة التالية" msgstr "الصفحة التالية"
# src/template_utils.rs:363 # src/template_utils.rs:365
msgid "Optional" msgid "Optional"
msgstr "اختياري" msgstr "اختياري"
# src/routes/blogs.rs:67 # src/routes/blogs.rs:68
msgid "To create a new blog, you need to be logged in" msgid "To create a new blog, you need to be logged in"
msgstr "لإنشاء مدونة جديدة، تحتاج إلى تسجيل الدخول" msgstr "لإنشاء مدونة جديدة، تحتاج إلى تسجيل الدخول"
# src/routes/blogs.rs:109 # src/routes/blogs.rs:110
msgid "A blog with the same name already exists." msgid "A blog with the same name already exists."
msgstr "توجد مدونة تحمل نفس العنوان." msgstr "توجد مدونة تحمل نفس العنوان."
# src/routes/blogs.rs:147 # src/routes/blogs.rs:148
msgid "Your blog was successfully created!" msgid "Your blog was successfully created!"
msgstr "تم إنشاء مدونتك بنجاح!" msgstr "تم إنشاء مدونتك بنجاح!"
# src/routes/blogs.rs:165 # src/routes/blogs.rs:166
msgid "Your blog was deleted." msgid "Your blog was deleted."
msgstr "تم حذف مدونتك." msgstr "تم حذف مدونتك."
# src/routes/blogs.rs:173 # src/routes/blogs.rs:174
msgid "You are not allowed to delete this blog." msgid "You are not allowed to delete this blog."
msgstr "لا يسمح لك بحذف هذه المدونة." msgstr "لا يسمح لك بحذف هذه المدونة."
# src/routes/blogs.rs:223 # src/routes/blogs.rs:224
msgid "You are not allowed to edit this blog." msgid "You are not allowed to edit this blog."
msgstr "لا يسمح لك بتعديل هذه المدونة." msgstr "لا يسمح لك بتعديل هذه المدونة."
# src/routes/blogs.rs:279 # src/routes/blogs.rs:280
msgid "You can't use this media as a blog icon." msgid "You can't use this media as a blog icon."
msgstr "لا يمكنك استخدام هذه الوسائط كأيقونة للمدونة." msgstr "لا يمكنك استخدام هذه الوسائط كأيقونة للمدونة."
# src/routes/blogs.rs:297 # src/routes/blogs.rs:298
msgid "You can't use this media as a blog banner." msgid "You can't use this media as a blog banner."
msgstr "لا يمكنك استخدام هذه الوسائط كشعار للمدونة." msgstr "لا يمكنك استخدام هذه الوسائط كشعار للمدونة."
# src/routes/blogs.rs:331 # src/routes/blogs.rs:332
msgid "Your blog information have been updated." msgid "Your blog information have been updated."
msgstr "تم تحديث معلومات مُدوّنتك." msgstr "تم تحديث معلومات مُدوّنتك."
@@ -109,6 +113,22 @@ msgstr "تم نشر تعليقك."
msgid "Your comment has been deleted." msgid "Your comment has been deleted."
msgstr "تم حذف تعليقك." msgstr "تم حذف تعليقك."
# src/routes/email_signups.rs:82
msgid "Registrations are closed on this instance."
msgstr "التسجيلات مُغلقة على مثيل الخادم هذ."
# src/routes/email_signups.rs:119
msgid "User registration"
msgstr ""
# src/routes/email_signups.rs:120
msgid "Here is the link for registration: {0}"
msgstr ""
# src/routes/email_signups.rs:219
msgid "Your account has been created. Now you just need to log in, before you can use it."
msgstr "لقد تم إنشاء حسابك. ما عليك إلّا الولوج الآن للتمكّن مِن استعماله."
# src/routes/instance.rs:117 # src/routes/instance.rs:117
msgid "Instance settings have been saved." msgid "Instance settings have been saved."
msgstr "تم حفظ إعدادات المثيل." msgstr "تم حفظ إعدادات المثيل."
@@ -149,19 +169,19 @@ msgstr "تم."
msgid "To like a post, you need to be logged in" msgid "To like a post, you need to be logged in"
msgstr "يجب عليك تسجيل الدخول أولا للإعجاب بهذا المقال" msgstr "يجب عليك تسجيل الدخول أولا للإعجاب بهذا المقال"
# src/routes/medias.rs:158 # src/routes/medias.rs:153
msgid "Your media have been deleted." msgid "Your media have been deleted."
msgstr "لقد تم حذف وسائطك." msgstr "لقد تم حذف وسائطك."
# src/routes/medias.rs:163 # src/routes/medias.rs:158
msgid "You are not allowed to delete this media." msgid "You are not allowed to delete this media."
msgstr "لا يسمح لك بحذف هذه الوسائط." msgstr "لا يسمح لك بحذف هذه الوسائط."
# src/routes/medias.rs:180 # src/routes/medias.rs:175
msgid "Your avatar has been updated." msgid "Your avatar has been updated."
msgstr "تم تحديث صورتك الشخصية." msgstr "تم تحديث صورتك الشخصية."
# src/routes/medias.rs:185 # src/routes/medias.rs:180
msgid "You are not allowed to use this media." msgid "You are not allowed to use this media."
msgstr "لا يسمح لك باستعمال هذه الوسائط." msgstr "لا يسمح لك باستعمال هذه الوسائط."
@@ -169,55 +189,55 @@ msgstr "لا يسمح لك باستعمال هذه الوسائط."
msgid "To see your notifications, you need to be logged in" msgid "To see your notifications, you need to be logged in"
msgstr "يجب عليك تسجيل الدخول أولا لعرض الإشعارات" msgstr "يجب عليك تسجيل الدخول أولا لعرض الإشعارات"
# src/routes/posts.rs:55 # src/routes/posts.rs:56
msgid "This post isn't published yet." msgid "This post isn't published yet."
msgstr "هذا المقال ليس منشورا بعد." msgstr "هذا المقال ليس منشورا بعد."
# src/routes/posts.rs:125 # src/routes/posts.rs:126
msgid "To write a new post, you need to be logged in" msgid "To write a new post, you need to be logged in"
msgstr "يجب عليك تسجيل الدخول أولا لكتابة مقال جديد" msgstr "يجب عليك تسجيل الدخول أولا لكتابة مقال جديد"
# src/routes/posts.rs:146 # src/routes/posts.rs:147
msgid "You are not an author of this blog." msgid "You are not an author of this blog."
msgstr "لست مِن محرري هذه المدونة." msgstr "لست مِن محرري هذه المدونة."
# src/routes/posts.rs:153 # src/routes/posts.rs:154
msgid "New post" msgid "New post"
msgstr "منشور جديد" msgstr "منشور جديد"
# src/routes/posts.rs:198 # src/routes/posts.rs:199
msgid "Edit {0}" msgid "Edit {0}"
msgstr "تعديل {0}" msgstr "تعديل {0}"
# src/routes/posts.rs:267 # src/routes/posts.rs:268
msgid "You are not allowed to publish on this blog." msgid "You are not allowed to publish on this blog."
msgstr "لا يسمح لك بالنشر على هذه المدونة." msgstr "لا يسمح لك بالنشر على هذه المدونة."
# src/routes/posts.rs:367 # src/routes/posts.rs:368
msgid "Your article has been updated." msgid "Your article has been updated."
msgstr "تم تحديث مقالك." msgstr "تم تحديث مقالك."
# src/routes/posts.rs:556 # src/routes/posts.rs:557
msgid "Your article has been saved." msgid "Your article has been saved."
msgstr "تم حفظ مقالك." msgstr "تم حفظ مقالك."
# src/routes/posts.rs:563 # src/routes/posts.rs:564
msgid "New article" msgid "New article"
msgstr "مقال جديد" msgstr "مقال جديد"
# src/routes/posts.rs:601 # src/routes/posts.rs:602
msgid "You are not allowed to delete this article." msgid "You are not allowed to delete this article."
msgstr "لا يسمح لك بحذف هذا المقال." msgstr "لا يسمح لك بحذف هذا المقال."
# src/routes/posts.rs:625 # src/routes/posts.rs:626
msgid "Your article has been deleted." msgid "Your article has been deleted."
msgstr "تم حذف مقالك." msgstr "تم حذف مقالك."
# src/routes/posts.rs:630 # src/routes/posts.rs:631
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?" msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
msgstr "لم يتم العثور على المقال الذي تحاول حذفه. ربما سبق حذفه؟" msgstr "لم يتم العثور على المقال الذي تحاول حذفه. ربما سبق حذفه؟"
# src/routes/posts.rs:672 # src/routes/posts.rs:673
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct." msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
msgstr "تعذر العثور عن معلومات حسابك. المرجو التحقق من صحة إسم المستخدم." msgstr "تعذر العثور عن معلومات حسابك. المرجو التحقق من صحة إسم المستخدم."
@@ -245,45 +265,70 @@ msgstr "ها هو رابط إعادة تعيين كلمتك السرية: {0}"
msgid "Your password was successfully reset." msgid "Your password was successfully reset."
msgstr "تمت إعادة تعيين كلمتك السرية بنجاح." msgstr "تمت إعادة تعيين كلمتك السرية بنجاح."
# src/routes/user.rs:74 # src/routes/user.rs:87
msgid "To access your dashboard, you need to be logged in" msgid "To access your dashboard, you need to be logged in"
msgstr "يجب عليك تسجيل الدخول أولاللنفاذ إلى لوح المراقبة" msgstr "يجب عليك تسجيل الدخول أولاللنفاذ إلى لوح المراقبة"
# src/routes/user.rs:96 # src/routes/user.rs:109
msgid "You are no longer following {}." msgid "You are no longer following {}."
msgstr "أنت لم تعد تتابع {}." msgstr "أنت لم تعد تتابع {}."
# src/routes/user.rs:113 # src/routes/user.rs:126
msgid "You are now following {}." msgid "You are now following {}."
msgstr "أنت الآن تتابع {}." msgstr "أنت الآن تتابع {}."
# src/routes/user.rs:190 # src/routes/user.rs:203
msgid "To subscribe to someone, you need to be logged in" msgid "To subscribe to someone, you need to be logged in"
msgstr "للإشتراك بأحد ما، يجب تسجيل الدخول أولا" msgstr "للإشتراك بأحد ما، يجب تسجيل الدخول أولا"
# src/routes/user.rs:299 # src/routes/user.rs:323
msgid "To edit your profile, you need to be logged in" msgid "To edit your profile, you need to be logged in"
msgstr "لتعديل الحساب، يجب تسجيل الدخول أولا" msgstr "لتعديل الحساب، يجب تسجيل الدخول أولا"
# src/routes/user.rs:345 # src/routes/user.rs:369
msgid "Your profile has been updated." msgid "Your profile has been updated."
msgstr "تم تحديث ملفك الشخصي." msgstr "تم تحديث ملفك الشخصي."
# src/routes/user.rs:373 # src/routes/user.rs:397
msgid "Your account has been deleted." msgid "Your account has been deleted."
msgstr "تم حذف حسابك." msgstr "تم حذف حسابك."
# src/routes/user.rs:379 # src/routes/user.rs:403
msgid "You can't delete someone else's account." msgid "You can't delete someone else's account."
msgstr "لا يمكنك حذف حساب شخص آخر." msgstr "لا يمكنك حذف حساب شخص آخر."
# src/routes/user.rs:463 msgid "Create your account"
msgid "Registrations are closed on this instance." msgstr "انشئ حسابك"
msgstr "التسجيلات مُغلقة على مثيل الخادم هذ."
# src/routes/user.rs:486 msgid "Create an account"
msgid "Your account has been created. Now you just need to log in, before you can use it." msgstr "انشئ حسابا"
msgstr "لقد تم إنشاء حسابك. ما عليك إلّا الولوج الآن للتمكّن مِن استعماله."
msgid "Email"
msgstr "البريد الالكتروني"
msgid "Email confirmation"
msgstr ""
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "المعذرة، لاكن التسجيل مغلق في هذا المثيل بالدات. يمكنك إجاد مثيل آخر للتسجيل."
msgid "Registration"
msgstr ""
msgid "Check your inbox!"
msgstr "تحقق من علبة الوارد الخاصة بك!"
msgid "We sent a mail to the address you gave us, with a link for registration."
msgstr ""
msgid "Username"
msgstr "اسم المستخدم"
msgid "Password"
msgstr "كلمة السر"
msgid "Password confirmation"
msgstr ""
msgid "Media upload" msgid "Media upload"
msgstr "إرسال الوسائط" msgstr "إرسال الوسائط"
@@ -423,27 +468,6 @@ msgstr "المشترِكون"
msgid "Subscriptions" msgid "Subscriptions"
msgstr "الاشتراكات" msgstr "الاشتراكات"
msgid "Create your account"
msgstr "انشئ حسابك"
msgid "Create an account"
msgstr "انشئ حسابا"
msgid "Username"
msgstr "اسم المستخدم"
msgid "Email"
msgstr "البريد الالكتروني"
msgid "Password"
msgstr "كلمة السر"
msgid "Password confirmation"
msgstr ""
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "المعذرة، لاكن التسجيل مغلق في هذا المثيل بالدات. يمكنك إجاد مثيل آخر للتسجيل."
msgid "{0}'s subscriptions" msgid "{0}'s subscriptions"
msgstr "{0} اشتراكات" msgstr "{0} اشتراكات"
@@ -849,9 +873,6 @@ msgstr "تأكيد"
msgid "Update password" msgid "Update password"
msgstr "تحديث الكلمة السرية" msgstr "تحديث الكلمة السرية"
msgid "Check your inbox!"
msgstr "تحقق من علبة الوارد الخاصة بك!"
msgid "We sent a mail to the address you gave us, with a link to reset your password." msgid "We sent a mail to the address you gave us, with a link to reset your password."
msgstr "لقد أرسلنا رسالة للعنوان الذي توصلنا به من طرفك تضمنرابط لإعادت تحديد كلمة المرور." msgstr "لقد أرسلنا رسالة للعنوان الذي توصلنا به من طرفك تضمنرابط لإعادت تحديد كلمة المرور."
+97 -76
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:28\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Bulgarian\n" "Language-Team: Bulgarian\n"
"Language: bg_BG\n" "Language: bg_BG\n"
@@ -18,86 +18,90 @@ msgstr ""
"X-Crowdin-File-ID: 8\n" "X-Crowdin-File-ID: 8\n"
# src/template_utils.rs:105 # src/template_utils.rs:105
msgid "Someone"
msgstr ""
# src/template_utils.rs:107
msgid "{0} commented on your article." msgid "{0} commented on your article."
msgstr "{0} коментира(ха) твоя статия." msgstr "{0} коментира(ха) твоя статия."
# src/template_utils.rs:106 # src/template_utils.rs:108
msgid "{0} is subscribed to you." msgid "{0} is subscribed to you."
msgstr "{0} абониран(и) за вас." msgstr "{0} абониран(и) за вас."
# src/template_utils.rs:107 # src/template_utils.rs:109
msgid "{0} liked your article." msgid "{0} liked your article."
msgstr "{0} хареса(ха) вашата статия." msgstr "{0} хареса(ха) вашата статия."
# src/template_utils.rs:108 # src/template_utils.rs:110
msgid "{0} mentioned you." msgid "{0} mentioned you."
msgstr "{0} ви спомена(ха)." msgstr "{0} ви спомена(ха)."
# src/template_utils.rs:109 # src/template_utils.rs:111
msgid "{0} boosted your article." msgid "{0} boosted your article."
msgstr "{0} подсили(ха) вашата статия." msgstr "{0} подсили(ха) вашата статия."
# src/template_utils.rs:116 # src/template_utils.rs:118
msgid "Your feed" msgid "Your feed"
msgstr "Вашата емисия" msgstr "Вашата емисия"
# src/template_utils.rs:117 # src/template_utils.rs:119
msgid "Local feed" msgid "Local feed"
msgstr "Местна емисия" msgstr "Местна емисия"
# src/template_utils.rs:118 # src/template_utils.rs:120
msgid "Federated feed" msgid "Federated feed"
msgstr "Федерална емисия" msgstr "Федерална емисия"
# src/template_utils.rs:154 # src/template_utils.rs:156
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "Аватар {0}" msgstr "Аватар {0}"
# src/template_utils.rs:198 # src/template_utils.rs:200
msgid "Previous page" msgid "Previous page"
msgstr "Предишна страница" msgstr "Предишна страница"
# src/template_utils.rs:209 # src/template_utils.rs:211
msgid "Next page" msgid "Next page"
msgstr "Следваща страница" msgstr "Следваща страница"
# src/template_utils.rs:363 # src/template_utils.rs:365
msgid "Optional" msgid "Optional"
msgstr "По избор" msgstr "По избор"
# src/routes/blogs.rs:67 # src/routes/blogs.rs:68
msgid "To create a new blog, you need to be logged in" msgid "To create a new blog, you need to be logged in"
msgstr "За да създадете нов блог, трябва да влезете" msgstr "За да създадете нов блог, трябва да влезете"
# src/routes/blogs.rs:109 # src/routes/blogs.rs:110
msgid "A blog with the same name already exists." msgid "A blog with the same name already exists."
msgstr "Вече съществува блог със същото име." msgstr "Вече съществува блог със същото име."
# src/routes/blogs.rs:147 # src/routes/blogs.rs:148
msgid "Your blog was successfully created!" msgid "Your blog was successfully created!"
msgstr "Блогът Ви бе успешно създаден!" msgstr "Блогът Ви бе успешно създаден!"
# src/routes/blogs.rs:165 # src/routes/blogs.rs:166
msgid "Your blog was deleted." msgid "Your blog was deleted."
msgstr "Блогът ви бе изтрит." msgstr "Блогът ви бе изтрит."
# src/routes/blogs.rs:173 # src/routes/blogs.rs:174
msgid "You are not allowed to delete this blog." msgid "You are not allowed to delete this blog."
msgstr "Нямате права за да изтриете този блог." msgstr "Нямате права за да изтриете този блог."
# src/routes/blogs.rs:223 # src/routes/blogs.rs:224
msgid "You are not allowed to edit this blog." msgid "You are not allowed to edit this blog."
msgstr "Нямате права за да редактирате този блог." msgstr "Нямате права за да редактирате този блог."
# src/routes/blogs.rs:279 # src/routes/blogs.rs:280
msgid "You can't use this media as a blog icon." msgid "You can't use this media as a blog icon."
msgstr "Не можете да използвате тази медия като икона на блога." msgstr "Не можете да използвате тази медия като икона на блога."
# src/routes/blogs.rs:297 # src/routes/blogs.rs:298
msgid "You can't use this media as a blog banner." msgid "You can't use this media as a blog banner."
msgstr "Не можете да използвате тази медия като банер на блога." msgstr "Не можете да използвате тази медия като банер на блога."
# src/routes/blogs.rs:331 # src/routes/blogs.rs:332
msgid "Your blog information have been updated." msgid "Your blog information have been updated."
msgstr "Информацията в блога ви бе актуализирана." msgstr "Информацията в блога ви бе актуализирана."
@@ -109,6 +113,22 @@ msgstr "Коментарът е публикуван."
msgid "Your comment has been deleted." msgid "Your comment has been deleted."
msgstr "Коментарът бе изтрит." msgstr "Коментарът бе изтрит."
# src/routes/email_signups.rs:82
msgid "Registrations are closed on this instance."
msgstr "Регистрациите са затворени в тази инстанция."
# src/routes/email_signups.rs:119
msgid "User registration"
msgstr ""
# src/routes/email_signups.rs:120
msgid "Here is the link for registration: {0}"
msgstr ""
# src/routes/email_signups.rs:219
msgid "Your account has been created. Now you just need to log in, before you can use it."
msgstr "Вашият акаунт беше създаден. Сега просто трябва да влезете за да можете да го използвате."
# src/routes/instance.rs:117 # src/routes/instance.rs:117
msgid "Instance settings have been saved." msgid "Instance settings have been saved."
msgstr "Настройките на инстанциите са запазени." msgstr "Настройките на инстанциите са запазени."
@@ -149,19 +169,19 @@ msgstr "Свършен."
msgid "To like a post, you need to be logged in" msgid "To like a post, you need to be logged in"
msgstr "За да харесате публикация, трябва да сте влезли в профила си" msgstr "За да харесате публикация, трябва да сте влезли в профила си"
# src/routes/medias.rs:158 # src/routes/medias.rs:153
msgid "Your media have been deleted." msgid "Your media have been deleted."
msgstr "Медията ви е изтрита." msgstr "Медията ви е изтрита."
# src/routes/medias.rs:163 # src/routes/medias.rs:158
msgid "You are not allowed to delete this media." msgid "You are not allowed to delete this media."
msgstr "Вие нямате права да изтриете тази медия." msgstr "Вие нямате права да изтриете тази медия."
# src/routes/medias.rs:180 # src/routes/medias.rs:175
msgid "Your avatar has been updated." msgid "Your avatar has been updated."
msgstr "Вашият аватар е актуализиран." msgstr "Вашият аватар е актуализиран."
# src/routes/medias.rs:185 # src/routes/medias.rs:180
msgid "You are not allowed to use this media." msgid "You are not allowed to use this media."
msgstr "Нямате права да използвате тази медия." msgstr "Нямате права да използвате тази медия."
@@ -169,55 +189,55 @@ msgstr "Нямате права да използвате тази медия."
msgid "To see your notifications, you need to be logged in" msgid "To see your notifications, you need to be logged in"
msgstr "За да видите известията си, трябва да сте влезли в профила си" msgstr "За да видите известията си, трябва да сте влезли в профила си"
# src/routes/posts.rs:55 # src/routes/posts.rs:56
msgid "This post isn't published yet." msgid "This post isn't published yet."
msgstr "Тази публикация все още не е публикувана." msgstr "Тази публикация все още не е публикувана."
# src/routes/posts.rs:125 # src/routes/posts.rs:126
msgid "To write a new post, you need to be logged in" msgid "To write a new post, you need to be logged in"
msgstr "За да напишете нова публикация, трябва да влезете" msgstr "За да напишете нова публикация, трябва да влезете"
# src/routes/posts.rs:146 # src/routes/posts.rs:147
msgid "You are not an author of this blog." msgid "You are not an author of this blog."
msgstr "Вие не сте автор на този блог." msgstr "Вие не сте автор на този блог."
# src/routes/posts.rs:153 # src/routes/posts.rs:154
msgid "New post" msgid "New post"
msgstr "Нова публикация" msgstr "Нова публикация"
# src/routes/posts.rs:198 # src/routes/posts.rs:199
msgid "Edit {0}" msgid "Edit {0}"
msgstr "Редактирано от {0}" msgstr "Редактирано от {0}"
# src/routes/posts.rs:267 # src/routes/posts.rs:268
msgid "You are not allowed to publish on this blog." msgid "You are not allowed to publish on this blog."
msgstr "Нямате права за публикуване в този блог." msgstr "Нямате права за публикуване в този блог."
# src/routes/posts.rs:367 # src/routes/posts.rs:368
msgid "Your article has been updated." msgid "Your article has been updated."
msgstr "Статията ви е актуализирана." msgstr "Статията ви е актуализирана."
# src/routes/posts.rs:556 # src/routes/posts.rs:557
msgid "Your article has been saved." msgid "Your article has been saved."
msgstr "Вашата статия е запазена." msgstr "Вашата статия е запазена."
# src/routes/posts.rs:563 # src/routes/posts.rs:564
msgid "New article" msgid "New article"
msgstr "Нова статия" msgstr "Нова статия"
# src/routes/posts.rs:601 # src/routes/posts.rs:602
msgid "You are not allowed to delete this article." msgid "You are not allowed to delete this article."
msgstr "Нямате права за изтриване на тази статия." msgstr "Нямате права за изтриване на тази статия."
# src/routes/posts.rs:625 # src/routes/posts.rs:626
msgid "Your article has been deleted." msgid "Your article has been deleted."
msgstr "Статията е изтрита." msgstr "Статията е изтрита."
# src/routes/posts.rs:630 # src/routes/posts.rs:631
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?" msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
msgstr "Изглежда, че статията, която се опитвате да изтриете не съществува. Може би вече я няма?" msgstr "Изглежда, че статията, която се опитвате да изтриете не съществува. Може би вече я няма?"
# src/routes/posts.rs:672 # src/routes/posts.rs:673
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct." msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
msgstr "Не можа да се получи достатъчно информация за профила ви. Моля, уверете се, че потребителското ви име е правилно." msgstr "Не можа да се получи достатъчно информация за профила ви. Моля, уверете се, че потребителското ви име е правилно."
@@ -245,45 +265,70 @@ msgstr "Ето и връзка, на която да зададете нова
msgid "Your password was successfully reset." msgid "Your password was successfully reset."
msgstr "Вашата парола бе успешно възстановена." msgstr "Вашата парола бе успешно възстановена."
# src/routes/user.rs:74 # src/routes/user.rs:87
msgid "To access your dashboard, you need to be logged in" msgid "To access your dashboard, you need to be logged in"
msgstr "За да получите достъп до таблото си за управление, трябва да сте влезли в профила си" msgstr "За да получите достъп до таблото си за управление, трябва да сте влезли в профила си"
# src/routes/user.rs:96 # src/routes/user.rs:109
msgid "You are no longer following {}." msgid "You are no longer following {}."
msgstr "Вече не следвате {}." msgstr "Вече не следвате {}."
# src/routes/user.rs:113 # src/routes/user.rs:126
msgid "You are now following {}." msgid "You are now following {}."
msgstr "Вече следите {}." msgstr "Вече следите {}."
# src/routes/user.rs:190 # src/routes/user.rs:203
msgid "To subscribe to someone, you need to be logged in" msgid "To subscribe to someone, you need to be logged in"
msgstr "За да се абонирате за някого, трябва да сте влезли в системата" msgstr "За да се абонирате за някого, трябва да сте влезли в системата"
# src/routes/user.rs:299 # src/routes/user.rs:323
msgid "To edit your profile, you need to be logged in" msgid "To edit your profile, you need to be logged in"
msgstr "За да редактирате профила си, трябва да влезете" msgstr "За да редактирате профила си, трябва да влезете"
# src/routes/user.rs:345 # src/routes/user.rs:369
msgid "Your profile has been updated." msgid "Your profile has been updated."
msgstr "Вашият профил е актуализиран." msgstr "Вашият профил е актуализиран."
# src/routes/user.rs:373 # src/routes/user.rs:397
msgid "Your account has been deleted." msgid "Your account has been deleted."
msgstr "Вашият акаунт е изтрит." msgstr "Вашият акаунт е изтрит."
# src/routes/user.rs:379 # src/routes/user.rs:403
msgid "You can't delete someone else's account." msgid "You can't delete someone else's account."
msgstr "Не можете да изтриете профила на някой друг." msgstr "Не можете да изтриете профила на някой друг."
# src/routes/user.rs:463 msgid "Create your account"
msgid "Registrations are closed on this instance." msgstr "Създай профил"
msgstr "Регистрациите са затворени в тази инстанция."
# src/routes/user.rs:486 msgid "Create an account"
msgid "Your account has been created. Now you just need to log in, before you can use it." msgstr "Създай профил"
msgstr "Вашият акаунт беше създаден. Сега просто трябва да влезете за да можете да го използвате."
msgid "Email"
msgstr "Електронна поща"
msgid "Email confirmation"
msgstr ""
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "Извиняваме се, но регистрациите са затворени за тази конкретна инстанция. Можете обаче да намерите друга."
msgid "Registration"
msgstr ""
msgid "Check your inbox!"
msgstr "Проверете си пощата!"
msgid "We sent a mail to the address you gave us, with a link for registration."
msgstr ""
msgid "Username"
msgstr "Потребителско име"
msgid "Password"
msgstr "Парола"
msgid "Password confirmation"
msgstr "Потвърждение на парола"
msgid "Media upload" msgid "Media upload"
msgstr "Качи медия" msgstr "Качи медия"
@@ -423,27 +468,6 @@ msgstr "Абонати"
msgid "Subscriptions" msgid "Subscriptions"
msgstr "Абонаменти" msgstr "Абонаменти"
msgid "Create your account"
msgstr "Създай профил"
msgid "Create an account"
msgstr "Създай профил"
msgid "Username"
msgstr "Потребителско име"
msgid "Email"
msgstr "Електронна поща"
msgid "Password"
msgstr "Парола"
msgid "Password confirmation"
msgstr "Потвърждение на парола"
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "Извиняваме се, но регистрациите са затворени за тази конкретна инстанция. Можете обаче да намерите друга."
msgid "{0}'s subscriptions" msgid "{0}'s subscriptions"
msgstr "{0} абонаменти" msgstr "{0} абонаменти"
@@ -841,9 +865,6 @@ msgstr "Потвърждение"
msgid "Update password" msgid "Update password"
msgstr "Обнови паролата" msgstr "Обнови паролата"
msgid "Check your inbox!"
msgstr "Проверете си пощата!"
msgid "We sent a mail to the address you gave us, with a link to reset your password." msgid "We sent a mail to the address you gave us, with a link to reset your password."
msgstr "Изпратихме емайл с връзка за възстановяване на паролата ви, на адреса който ни дадохте." msgstr "Изпратихме емайл с връзка за възстановяване на паролата ви, на адреса който ни дадохте."
+97 -76
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:28\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Catalan\n" "Language-Team: Catalan\n"
"Language: ca_ES\n" "Language: ca_ES\n"
@@ -18,86 +18,90 @@ msgstr ""
"X-Crowdin-File-ID: 8\n" "X-Crowdin-File-ID: 8\n"
# src/template_utils.rs:105 # src/template_utils.rs:105
msgid "Someone"
msgstr ""
# src/template_utils.rs:107
msgid "{0} commented on your article." msgid "{0} commented on your article."
msgstr "{0} han comentat el teu article." msgstr "{0} han comentat el teu article."
# src/template_utils.rs:106 # src/template_utils.rs:108
msgid "{0} is subscribed to you." msgid "{0} is subscribed to you."
msgstr "{0} s'ha subscrit." msgstr "{0} s'ha subscrit."
# src/template_utils.rs:107 # src/template_utils.rs:109
msgid "{0} liked your article." msgid "{0} liked your article."
msgstr "A {0} li ha agradat el vostre article." msgstr "A {0} li ha agradat el vostre article."
# src/template_utils.rs:108 # src/template_utils.rs:110
msgid "{0} mentioned you." msgid "{0} mentioned you."
msgstr "{0} us ha esmentat." msgstr "{0} us ha esmentat."
# src/template_utils.rs:109 # src/template_utils.rs:111
msgid "{0} boosted your article." msgid "{0} boosted your article."
msgstr "{0} ha impulsat el teu article." msgstr "{0} ha impulsat el teu article."
# src/template_utils.rs:116 # src/template_utils.rs:118
msgid "Your feed" msgid "Your feed"
msgstr "El teu feed" msgstr "El teu feed"
# src/template_utils.rs:117 # src/template_utils.rs:119
msgid "Local feed" msgid "Local feed"
msgstr "Feed local" msgstr "Feed local"
# src/template_utils.rs:118 # src/template_utils.rs:120
msgid "Federated feed" msgid "Federated feed"
msgstr "Feed federat" msgstr "Feed federat"
# src/template_utils.rs:154 # src/template_utils.rs:156
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "avatar de {0}" msgstr "avatar de {0}"
# src/template_utils.rs:198 # src/template_utils.rs:200
msgid "Previous page" msgid "Previous page"
msgstr "Pàgina anterior" msgstr "Pàgina anterior"
# src/template_utils.rs:209 # src/template_utils.rs:211
msgid "Next page" msgid "Next page"
msgstr "Pàgina següent" msgstr "Pàgina següent"
# src/template_utils.rs:363 # src/template_utils.rs:365
msgid "Optional" msgid "Optional"
msgstr "Opcional" msgstr "Opcional"
# src/routes/blogs.rs:67 # src/routes/blogs.rs:68
msgid "To create a new blog, you need to be logged in" msgid "To create a new blog, you need to be logged in"
msgstr "Per a crear un blog nou, heu diniciar una sessió" msgstr "Per a crear un blog nou, heu diniciar una sessió"
# src/routes/blogs.rs:109 # src/routes/blogs.rs:110
msgid "A blog with the same name already exists." msgid "A blog with the same name already exists."
msgstr "Ja existeix un blog amb el mateix nom." msgstr "Ja existeix un blog amb el mateix nom."
# src/routes/blogs.rs:147 # src/routes/blogs.rs:148
msgid "Your blog was successfully created!" msgid "Your blog was successfully created!"
msgstr "Sha creat el vostre blog correctament." msgstr "Sha creat el vostre blog correctament."
# src/routes/blogs.rs:165 # src/routes/blogs.rs:166
msgid "Your blog was deleted." msgid "Your blog was deleted."
msgstr "Sha suprimit el vostre blog." msgstr "Sha suprimit el vostre blog."
# src/routes/blogs.rs:173 # src/routes/blogs.rs:174
msgid "You are not allowed to delete this blog." msgid "You are not allowed to delete this blog."
msgstr "No tens permís per a esborrar aquest blog." msgstr "No tens permís per a esborrar aquest blog."
# src/routes/blogs.rs:223 # src/routes/blogs.rs:224
msgid "You are not allowed to edit this blog." msgid "You are not allowed to edit this blog."
msgstr "No tens permís per a editar aquest blog." msgstr "No tens permís per a editar aquest blog."
# src/routes/blogs.rs:279 # src/routes/blogs.rs:280
msgid "You can't use this media as a blog icon." msgid "You can't use this media as a blog icon."
msgstr "No pots usar aquest Mèdia com a icona del blog." msgstr "No pots usar aquest Mèdia com a icona del blog."
# src/routes/blogs.rs:297 # src/routes/blogs.rs:298
msgid "You can't use this media as a blog banner." msgid "You can't use this media as a blog banner."
msgstr "No pots usar aquest Mèdia com a capçalera del blog." msgstr "No pots usar aquest Mèdia com a capçalera del blog."
# src/routes/blogs.rs:331 # src/routes/blogs.rs:332
msgid "Your blog information have been updated." msgid "Your blog information have been updated."
msgstr "Sha actualitzat la informació del vostre blog." msgstr "Sha actualitzat la informació del vostre blog."
@@ -109,6 +113,22 @@ msgstr "Sha publicat el vostre comentari."
msgid "Your comment has been deleted." msgid "Your comment has been deleted."
msgstr "Sha suprimit el vostre comentari." msgstr "Sha suprimit el vostre comentari."
# src/routes/email_signups.rs:82
msgid "Registrations are closed on this instance."
msgstr "El registre d'aquesta instància és tancat."
# src/routes/email_signups.rs:119
msgid "User registration"
msgstr ""
# src/routes/email_signups.rs:120
msgid "Here is the link for registration: {0}"
msgstr ""
# src/routes/email_signups.rs:219
msgid "Your account has been created. Now you just need to log in, before you can use it."
msgstr "S'ha creat el teu compte. Ara cal iniciar sessió per a començar a usar-lo."
# src/routes/instance.rs:117 # src/routes/instance.rs:117
msgid "Instance settings have been saved." msgid "Instance settings have been saved."
msgstr "S'han desat les configuracions de l'instància." msgstr "S'han desat les configuracions de l'instància."
@@ -149,19 +169,19 @@ msgstr "Fet."
msgid "To like a post, you need to be logged in" msgid "To like a post, you need to be logged in"
msgstr "Per a agradar-te una publicació necessites iniciar sessió" msgstr "Per a agradar-te una publicació necessites iniciar sessió"
# src/routes/medias.rs:158 # src/routes/medias.rs:153
msgid "Your media have been deleted." msgid "Your media have been deleted."
msgstr "S'ha esborrat el teu Mèdia." msgstr "S'ha esborrat el teu Mèdia."
# src/routes/medias.rs:163 # src/routes/medias.rs:158
msgid "You are not allowed to delete this media." msgid "You are not allowed to delete this media."
msgstr "No tens permís per a esborrar aquest Mèdia." msgstr "No tens permís per a esborrar aquest Mèdia."
# src/routes/medias.rs:180 # src/routes/medias.rs:175
msgid "Your avatar has been updated." msgid "Your avatar has been updated."
msgstr "S'ha actualitzat el teu avatar." msgstr "S'ha actualitzat el teu avatar."
# src/routes/medias.rs:185 # src/routes/medias.rs:180
msgid "You are not allowed to use this media." msgid "You are not allowed to use this media."
msgstr "No tens permís per a usar aquest Mèdia." msgstr "No tens permís per a usar aquest Mèdia."
@@ -169,55 +189,55 @@ msgstr "No tens permís per a usar aquest Mèdia."
msgid "To see your notifications, you need to be logged in" msgid "To see your notifications, you need to be logged in"
msgstr "Per a veure les teves notificacions necessites iniciar sessió" msgstr "Per a veure les teves notificacions necessites iniciar sessió"
# src/routes/posts.rs:55 # src/routes/posts.rs:56
msgid "This post isn't published yet." msgid "This post isn't published yet."
msgstr "Aquesta entrada encara no està publicada." msgstr "Aquesta entrada encara no està publicada."
# src/routes/posts.rs:125 # src/routes/posts.rs:126
msgid "To write a new post, you need to be logged in" msgid "To write a new post, you need to be logged in"
msgstr "Per a escriure una nova entrada cal iniciar sessió" msgstr "Per a escriure una nova entrada cal iniciar sessió"
# src/routes/posts.rs:146 # src/routes/posts.rs:147
msgid "You are not an author of this blog." msgid "You are not an author of this blog."
msgstr "No ets un autor d'aquest blog." msgstr "No ets un autor d'aquest blog."
# src/routes/posts.rs:153 # src/routes/posts.rs:154
msgid "New post" msgid "New post"
msgstr "Apunt nou" msgstr "Apunt nou"
# src/routes/posts.rs:198 # src/routes/posts.rs:199
msgid "Edit {0}" msgid "Edit {0}"
msgstr "Edita {0}" msgstr "Edita {0}"
# src/routes/posts.rs:267 # src/routes/posts.rs:268
msgid "You are not allowed to publish on this blog." msgid "You are not allowed to publish on this blog."
msgstr "No tens permís per a publicar en aquest blog." msgstr "No tens permís per a publicar en aquest blog."
# src/routes/posts.rs:367 # src/routes/posts.rs:368
msgid "Your article has been updated." msgid "Your article has been updated."
msgstr "Sha actualitzat el vostre article." msgstr "Sha actualitzat el vostre article."
# src/routes/posts.rs:556 # src/routes/posts.rs:557
msgid "Your article has been saved." msgid "Your article has been saved."
msgstr "Sha desat el vostre article." msgstr "Sha desat el vostre article."
# src/routes/posts.rs:563 # src/routes/posts.rs:564
msgid "New article" msgid "New article"
msgstr "Article nou" msgstr "Article nou"
# src/routes/posts.rs:601 # src/routes/posts.rs:602
msgid "You are not allowed to delete this article." msgid "You are not allowed to delete this article."
msgstr "No tens permís per a esborrar aquest article." msgstr "No tens permís per a esborrar aquest article."
# src/routes/posts.rs:625 # src/routes/posts.rs:626
msgid "Your article has been deleted." msgid "Your article has been deleted."
msgstr "Sha suprimit el vostre article." msgstr "Sha suprimit el vostre article."
# src/routes/posts.rs:630 # src/routes/posts.rs:631
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?" msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
msgstr "Sembla que l'article que intentes esborrar no existeix. Potser ja no hi és?" msgstr "Sembla que l'article que intentes esborrar no existeix. Potser ja no hi és?"
# src/routes/posts.rs:672 # src/routes/posts.rs:673
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct." msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
msgstr "No s'ha pogut obtenir informació sobre el teu compte. Si us plau, assegura't que el teu nom d'usuari és correcte." msgstr "No s'ha pogut obtenir informació sobre el teu compte. Si us plau, assegura't que el teu nom d'usuari és correcte."
@@ -245,45 +265,70 @@ msgstr "Aquí està l'enllaç per a reiniciar la teva contrasenya: {0}"
msgid "Your password was successfully reset." msgid "Your password was successfully reset."
msgstr "Sha reinicialitzat la vostra contrasenya correctament." msgstr "Sha reinicialitzat la vostra contrasenya correctament."
# src/routes/user.rs:74 # src/routes/user.rs:87
msgid "To access your dashboard, you need to be logged in" msgid "To access your dashboard, you need to be logged in"
msgstr "Per a accedir al teu panell cal iniciar sessió" msgstr "Per a accedir al teu panell cal iniciar sessió"
# src/routes/user.rs:96 # src/routes/user.rs:109
msgid "You are no longer following {}." msgid "You are no longer following {}."
msgstr "Ja no segueixes a {}." msgstr "Ja no segueixes a {}."
# src/routes/user.rs:113 # src/routes/user.rs:126
msgid "You are now following {}." msgid "You are now following {}."
msgstr "Ara estàs seguint a {}." msgstr "Ara estàs seguint a {}."
# src/routes/user.rs:190 # src/routes/user.rs:203
msgid "To subscribe to someone, you need to be logged in" msgid "To subscribe to someone, you need to be logged in"
msgstr "Per a subscriure't a algú cal iniciar sessió" msgstr "Per a subscriure't a algú cal iniciar sessió"
# src/routes/user.rs:299 # src/routes/user.rs:323
msgid "To edit your profile, you need to be logged in" msgid "To edit your profile, you need to be logged in"
msgstr "Per a editar el teu perfil cal iniciar sessió" msgstr "Per a editar el teu perfil cal iniciar sessió"
# src/routes/user.rs:345 # src/routes/user.rs:369
msgid "Your profile has been updated." msgid "Your profile has been updated."
msgstr "El teu perfil s'ha actualitzat." msgstr "El teu perfil s'ha actualitzat."
# src/routes/user.rs:373 # src/routes/user.rs:397
msgid "Your account has been deleted." msgid "Your account has been deleted."
msgstr "El teu compte s'ha esborrat." msgstr "El teu compte s'ha esborrat."
# src/routes/user.rs:379 # src/routes/user.rs:403
msgid "You can't delete someone else's account." msgid "You can't delete someone else's account."
msgstr "No pots esborrar el compte d'algú altre." msgstr "No pots esborrar el compte d'algú altre."
# src/routes/user.rs:463 msgid "Create your account"
msgid "Registrations are closed on this instance." msgstr "Crea el teu compte"
msgstr "El registre d'aquesta instància és tancat."
# src/routes/user.rs:486 msgid "Create an account"
msgid "Your account has been created. Now you just need to log in, before you can use it." msgstr "Crear un compte"
msgstr "S'ha creat el teu compte. Ara cal iniciar sessió per a començar a usar-lo."
msgid "Email"
msgstr "Adreça electrònica"
msgid "Email confirmation"
msgstr ""
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "Disculpa, el registre d'aquesta instància és tancat. Pots trobar-ne un altre diferent."
msgid "Registration"
msgstr ""
msgid "Check your inbox!"
msgstr "Reviseu la vostra safata dentrada."
msgid "We sent a mail to the address you gave us, with a link for registration."
msgstr ""
msgid "Username"
msgstr "Nom dusuari"
msgid "Password"
msgstr "Contrasenya"
msgid "Password confirmation"
msgstr "Confirmació de la contrasenya"
msgid "Media upload" msgid "Media upload"
msgstr "Carregar Mèdia" msgstr "Carregar Mèdia"
@@ -423,27 +468,6 @@ msgstr "Subscriptors"
msgid "Subscriptions" msgid "Subscriptions"
msgstr "Subscripcions" msgstr "Subscripcions"
msgid "Create your account"
msgstr "Crea el teu compte"
msgid "Create an account"
msgstr "Crear un compte"
msgid "Username"
msgstr "Nom dusuari"
msgid "Email"
msgstr "Adreça electrònica"
msgid "Password"
msgstr "Contrasenya"
msgid "Password confirmation"
msgstr "Confirmació de la contrasenya"
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "Disculpa, el registre d'aquesta instància és tancat. Pots trobar-ne un altre diferent."
msgid "{0}'s subscriptions" msgid "{0}'s subscriptions"
msgstr "Subscripcions de {0}" msgstr "Subscripcions de {0}"
@@ -841,9 +865,6 @@ msgstr "Confirmació"
msgid "Update password" msgid "Update password"
msgstr "Actualitza la contrasenya" msgstr "Actualitza la contrasenya"
msgid "Check your inbox!"
msgstr "Reviseu la vostra safata dentrada."
msgid "We sent a mail to the address you gave us, with a link to reset your password." msgid "We sent a mail to the address you gave us, with a link to reset your password."
msgstr "Hem enviat un correu a l'adreça que ens vas donar, amb un enllaç per a reiniciar la teva contrasenya." msgstr "Hem enviat un correu a l'adreça que ens vas donar, amb un enllaç per a reiniciar la teva contrasenya."
+101 -80
View File
@@ -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-02 11:39\n" "PO-Revision-Date: 2022-05-09 09:58\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Czech\n" "Language-Team: Czech\n"
"Language: cs_CZ\n" "Language: cs_CZ\n"
@@ -18,86 +18,90 @@ msgstr ""
"X-Crowdin-File-ID: 8\n" "X-Crowdin-File-ID: 8\n"
# src/template_utils.rs:105 # src/template_utils.rs:105
msgid "Someone"
msgstr "Někdo"
# src/template_utils.rs:107
msgid "{0} commented on your article." msgid "{0} commented on your article."
msgstr "{0} komentoval/a váš článek." msgstr "{0} komentoval/a váš článek."
# src/template_utils.rs:106 # src/template_utils.rs:108
msgid "{0} is subscribed to you." msgid "{0} is subscribed to you."
msgstr "{0} vás odebírá." msgstr "{0} vás odebírá."
# src/template_utils.rs:107 # src/template_utils.rs:109
msgid "{0} liked your article." msgid "{0} liked your article."
msgstr "{0} si oblíbil/a váš článek." msgstr "{0} si oblíbil/a váš článek."
# src/template_utils.rs:108 # src/template_utils.rs:110
msgid "{0} mentioned you." msgid "{0} mentioned you."
msgstr "{0} vás zmínil/a." msgstr "{0} vás zmínil/a."
# src/template_utils.rs:109 # src/template_utils.rs:111
msgid "{0} boosted your article." msgid "{0} boosted your article."
msgstr "{0} povýšil/a váš článek." msgstr "{0} povýšil/a váš článek."
# src/template_utils.rs:116 # src/template_utils.rs:118
msgid "Your feed" msgid "Your feed"
msgstr "Vaše zdroje" msgstr "Vaše zdroje"
# src/template_utils.rs:117 # src/template_utils.rs:119
msgid "Local feed" msgid "Local feed"
msgstr "Místní zdroje" msgstr "Místní zdroje"
# src/template_utils.rs:118 # src/template_utils.rs:120
msgid "Federated feed" msgid "Federated feed"
msgstr "Federované zdroje" msgstr "Federované zdroje"
# src/template_utils.rs:154 # src/template_utils.rs:156
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "Avatar uživatele {0}" msgstr "Avatar uživatele {0}"
# src/template_utils.rs:198 # src/template_utils.rs:200
msgid "Previous page" msgid "Previous page"
msgstr "Předchozí stránka" msgstr "Předchozí stránka"
# src/template_utils.rs:209 # src/template_utils.rs:211
msgid "Next page" msgid "Next page"
msgstr "Následující strana" msgstr "Následující strana"
# src/template_utils.rs:363 # src/template_utils.rs:365
msgid "Optional" msgid "Optional"
msgstr "Volitelné" msgstr "Volitelné"
# src/routes/blogs.rs:67 # src/routes/blogs.rs:68
msgid "To create a new blog, you need to be logged in" msgid "To create a new blog, you need to be logged in"
msgstr "Pro vytvoření nového blogu musíte být přihlášeni" msgstr "Pro vytvoření nového blogu musíte být přihlášeni"
# src/routes/blogs.rs:109 # src/routes/blogs.rs:110
msgid "A blog with the same name already exists." msgid "A blog with the same name already exists."
msgstr "Blog s rovnakým názvem již existuje." msgstr "Blog s rovnakým názvem již existuje."
# src/routes/blogs.rs:147 # src/routes/blogs.rs:148
msgid "Your blog was successfully created!" msgid "Your blog was successfully created!"
msgstr "Váš blog byl úspěšně vytvořen!" msgstr "Váš blog byl úspěšně vytvořen!"
# src/routes/blogs.rs:165 # src/routes/blogs.rs:166
msgid "Your blog was deleted." msgid "Your blog was deleted."
msgstr "Váš blog byl smazán." msgstr "Váš blog byl smazán."
# src/routes/blogs.rs:173 # src/routes/blogs.rs:174
msgid "You are not allowed to delete this blog." msgid "You are not allowed to delete this blog."
msgstr "Nemáte oprávnění zmazat tento blog." msgstr "Nemáte oprávnění zmazat tento blog."
# src/routes/blogs.rs:223 # src/routes/blogs.rs:224
msgid "You are not allowed to edit this blog." msgid "You are not allowed to edit this blog."
msgstr "Nemáte oprávnění upravovat tento blog." msgstr "Nemáte oprávnění upravovat tento blog."
# src/routes/blogs.rs:279 # src/routes/blogs.rs:280
msgid "You can't use this media as a blog icon." msgid "You can't use this media as a blog icon."
msgstr "Toto médium nelze použít jako ikonu blogu." msgstr "Toto médium nelze použít jako ikonu blogu."
# src/routes/blogs.rs:297 # src/routes/blogs.rs:298
msgid "You can't use this media as a blog banner." msgid "You can't use this media as a blog banner."
msgstr "Toto médium nelze použít jako banner blogu." msgstr "Toto médium nelze použít jako banner blogu."
# src/routes/blogs.rs:331 # src/routes/blogs.rs:332
msgid "Your blog information have been updated." msgid "Your blog information have been updated."
msgstr "Údaje o vašem blogu byly aktualizovány." msgstr "Údaje o vašem blogu byly aktualizovány."
@@ -109,6 +113,22 @@ msgstr "Váš komentář byl zveřejněn."
msgid "Your comment has been deleted." msgid "Your comment has been deleted."
msgstr "Váš komentář byl odstraněn." msgstr "Váš komentář byl odstraněn."
# src/routes/email_signups.rs:82
msgid "Registrations are closed on this instance."
msgstr "Registrace jsou na téhle instanci uzavřeny."
# src/routes/email_signups.rs:119
msgid "User registration"
msgstr "Registrace uživatele"
# src/routes/email_signups.rs:120
msgid "Here is the link for registration: {0}"
msgstr "Zde je odkaz k registraci: {0}"
# src/routes/email_signups.rs:219
msgid "Your account has been created. Now you just need to log in, before you can use it."
msgstr "Váš účet byl vytvořen. Nyní se stačí jenom přihlásit, než ho budete moci používat."
# src/routes/instance.rs:117 # src/routes/instance.rs:117
msgid "Instance settings have been saved." msgid "Instance settings have been saved."
msgstr "Nastavení instance bylo uloženo." msgstr "Nastavení instance bylo uloženo."
@@ -149,19 +169,19 @@ msgstr "Hotovo."
msgid "To like a post, you need to be logged in" msgid "To like a post, you need to be logged in"
msgstr "Pro oblíbení příspěvku musíte být přihlášen/a" msgstr "Pro oblíbení příspěvku musíte být přihlášen/a"
# src/routes/medias.rs:158 # src/routes/medias.rs:153
msgid "Your media have been deleted." msgid "Your media have been deleted."
msgstr "Vaše média byla smazána." msgstr "Vaše média byla smazána."
# src/routes/medias.rs:163 # src/routes/medias.rs:158
msgid "You are not allowed to delete this media." msgid "You are not allowed to delete this media."
msgstr "Nemáte oprávnění k smazání tohoto média." msgstr "Nemáte oprávnění k smazání tohoto média."
# src/routes/medias.rs:180 # src/routes/medias.rs:175
msgid "Your avatar has been updated." msgid "Your avatar has been updated."
msgstr "Váš avatar byl aktualizován." msgstr "Váš avatar byl aktualizován."
# src/routes/medias.rs:185 # src/routes/medias.rs:180
msgid "You are not allowed to use this media." msgid "You are not allowed to use this media."
msgstr "Nemáte oprávnění k použití tohoto média." msgstr "Nemáte oprávnění k použití tohoto média."
@@ -169,55 +189,55 @@ msgstr "Nemáte oprávnění k použití tohoto média."
msgid "To see your notifications, you need to be logged in" msgid "To see your notifications, you need to be logged in"
msgstr "Pokud chcete vidět vaše notifikace, musíte být přihlášeni" msgstr "Pokud chcete vidět vaše notifikace, musíte být přihlášeni"
# src/routes/posts.rs:55 # src/routes/posts.rs:56
msgid "This post isn't published yet." msgid "This post isn't published yet."
msgstr "Tento příspěvek ještě není zveřejněn." msgstr "Tento příspěvek ještě není zveřejněn."
# src/routes/posts.rs:125 # src/routes/posts.rs:126
msgid "To write a new post, you need to be logged in" msgid "To write a new post, you need to be logged in"
msgstr "K napsaní nového příspěvku musíte být přihlášeni" msgstr "K napsaní nového příspěvku musíte být přihlášeni"
# src/routes/posts.rs:146 # src/routes/posts.rs:147
msgid "You are not an author of this blog." msgid "You are not an author of this blog."
msgstr "Nejste autorem tohto blogu." msgstr "Nejste autorem tohto blogu."
# src/routes/posts.rs:153 # src/routes/posts.rs:154
msgid "New post" msgid "New post"
msgstr "Nový příspěvek" msgstr "Nový příspěvek"
# src/routes/posts.rs:198 # src/routes/posts.rs:199
msgid "Edit {0}" msgid "Edit {0}"
msgstr "Upravit {0}" msgstr "Upravit {0}"
# src/routes/posts.rs:267 # src/routes/posts.rs:268
msgid "You are not allowed to publish on this blog." msgid "You are not allowed to publish on this blog."
msgstr "Nemáte oprávnění zveřejňovat na tomto blogu." msgstr "Nemáte oprávnění zveřejňovat na tomto blogu."
# src/routes/posts.rs:367 # src/routes/posts.rs:368
msgid "Your article has been updated." msgid "Your article has been updated."
msgstr "Váš článek byl upraven." msgstr "Váš článek byl upraven."
# src/routes/posts.rs:556 # src/routes/posts.rs:557
msgid "Your article has been saved." msgid "Your article has been saved."
msgstr "Váš článek byl uložen." msgstr "Váš článek byl uložen."
# src/routes/posts.rs:563 # src/routes/posts.rs:564
msgid "New article" msgid "New article"
msgstr "Nový článek" msgstr "Nový článek"
# src/routes/posts.rs:601 # src/routes/posts.rs:602
msgid "You are not allowed to delete this article." msgid "You are not allowed to delete this article."
msgstr "Nemáte oprávnění zmazat tento článek." msgstr "Nemáte oprávnění zmazat tento článek."
# src/routes/posts.rs:625 # src/routes/posts.rs:626
msgid "Your article has been deleted." msgid "Your article has been deleted."
msgstr "Váš článek byl smazán." msgstr "Váš článek byl smazán."
# src/routes/posts.rs:630 # src/routes/posts.rs:631
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?" msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
msgstr "Zdá se, že článek, který jste se snažili smazat, neexistuje, možná je již pryč?" msgstr "Zdá se, že článek, který jste se snažili smazat, neexistuje, možná je již pryč?"
# src/routes/posts.rs:672 # src/routes/posts.rs:673
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct." msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
msgstr "Nemohli jsme zjistit dostatečné množství informací ohledne vašeho účtu. Prosím ověřte si, že vaše předzývka je správná." msgstr "Nemohli jsme zjistit dostatečné množství informací ohledne vašeho účtu. Prosím ověřte si, že vaše předzývka je správná."
@@ -245,45 +265,70 @@ msgstr "Zde je odkaz na obnovení vášho hesla: {0}"
msgid "Your password was successfully reset." msgid "Your password was successfully reset."
msgstr "Vaše heslo bylo úspěšně obnoveno." msgstr "Vaše heslo bylo úspěšně obnoveno."
# src/routes/user.rs:74 # src/routes/user.rs:87
msgid "To access your dashboard, you need to be logged in" msgid "To access your dashboard, you need to be logged in"
msgstr "Pro přístup k vaší nástěnce musíte být přihlášen/a" msgstr "Pro přístup k vaší nástěnce musíte být přihlášen/a"
# src/routes/user.rs:96 # src/routes/user.rs:109
msgid "You are no longer following {}." msgid "You are no longer following {}."
msgstr "Již nenásledujete {}." msgstr "Již nenásledujete {}."
# src/routes/user.rs:113 # src/routes/user.rs:126
msgid "You are now following {}." msgid "You are now following {}."
msgstr "Teď již nenásledujete {}." msgstr "Teď již nenásledujete {}."
# src/routes/user.rs:190 # src/routes/user.rs:203
msgid "To subscribe to someone, you need to be logged in" msgid "To subscribe to someone, you need to be logged in"
msgstr "Chcete-li někoho odebírat, musíte být přihlášeni" msgstr "Chcete-li někoho odebírat, musíte být přihlášeni"
# src/routes/user.rs:299 # src/routes/user.rs:323
msgid "To edit your profile, you need to be logged in" msgid "To edit your profile, you need to be logged in"
msgstr "Pro úpravu vášho profilu musíte být přihlášeni" msgstr "Pro úpravu vášho profilu musíte být přihlášeni"
# src/routes/user.rs:345 # src/routes/user.rs:369
msgid "Your profile has been updated." msgid "Your profile has been updated."
msgstr "Váš profil byl upraven." msgstr "Váš profil byl upraven."
# src/routes/user.rs:373 # src/routes/user.rs:397
msgid "Your account has been deleted." msgid "Your account has been deleted."
msgstr "Váš účet byl odstraněn." msgstr "Váš účet byl odstraněn."
# src/routes/user.rs:379 # src/routes/user.rs:403
msgid "You can't delete someone else's account." msgid "You can't delete someone else's account."
msgstr "Nemůžete smazat účet někoho jiného." msgstr "Nemůžete smazat účet někoho jiného."
# src/routes/user.rs:463 msgid "Create your account"
msgid "Registrations are closed on this instance." msgstr "Vytvořit váš účet"
msgstr "Registrace jsou na téhle instanci uzavřeny."
# src/routes/user.rs:486 msgid "Create an account"
msgid "Your account has been created. Now you just need to log in, before you can use it." msgstr "Vytvořit účet"
msgstr "Váš účet byl vytvořen. Nyní se stačí jenom přihlásit, než ho budete moci používat."
msgid "Email"
msgstr "Email"
msgid "Email confirmation"
msgstr "Potvrzení emailem"
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "Omlouváme se, ale registrace je uzavřena na této konkrétní instanci. Můžete však najít jinou."
msgid "Registration"
msgstr "Registrace"
msgid "Check your inbox!"
msgstr "Zkontrolujte svou příchozí poštu!"
msgid "We sent a mail to the address you gave us, with a link for registration."
msgstr ""
msgid "Username"
msgstr "Uživatelské jméno"
msgid "Password"
msgstr "Heslo"
msgid "Password confirmation"
msgstr "Potvrzení hesla"
msgid "Media upload" msgid "Media upload"
msgstr "Nahrávaní médií" msgstr "Nahrávaní médií"
@@ -423,27 +468,6 @@ msgstr "Odběratelé"
msgid "Subscriptions" msgid "Subscriptions"
msgstr "Odběry" msgstr "Odběry"
msgid "Create your account"
msgstr "Vytvořit váš účet"
msgid "Create an account"
msgstr "Vytvořit účet"
msgid "Username"
msgstr "Uživatelské jméno"
msgid "Email"
msgstr "Email"
msgid "Password"
msgstr "Heslo"
msgid "Password confirmation"
msgstr "Potvrzení hesla"
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "Omlouváme se, ale registrace je uzavřena na této konkrétní instanci. Můžete však najít jinou."
msgid "{0}'s subscriptions" msgid "{0}'s subscriptions"
msgstr "Odběry uživatele {0}" msgstr "Odběry uživatele {0}"
@@ -640,16 +664,16 @@ msgid "Blocklisted Emails"
msgstr "" msgstr ""
msgid "Email address" msgid "Email address"
msgstr "" msgstr "Emailová adresa"
msgid "The email address you wish to block. In order to block domains, you can use globbing syntax, for example '*@example.com' blocks all addresses from example.com" msgid "The email address you wish to block. In order to block domains, you can use globbing syntax, for example '*@example.com' blocks all addresses from example.com"
msgstr "" msgstr ""
msgid "Note" msgid "Note"
msgstr "" msgstr "Poznámka"
msgid "Notify the user?" msgid "Notify the user?"
msgstr "" msgstr "Upozornit uživatele?"
msgid "Optional, shows a message to the user when they attempt to create an account with that address" msgid "Optional, shows a message to the user when they attempt to create an account with that address"
msgstr "" msgstr ""
@@ -845,9 +869,6 @@ msgstr "Potvrzení"
msgid "Update password" msgid "Update password"
msgstr "Aktualizovat heslo" msgstr "Aktualizovat heslo"
msgid "Check your inbox!"
msgstr "Zkontrolujte svou příchozí poštu!"
msgid "We sent a mail to the address you gave us, with a link to reset your password." msgid "We sent a mail to the address you gave us, with a link to reset your password."
msgstr "Zaslali jsme email na adresu, kterou jste nám dodali, s odkazem na obnovu vášho hesla." msgstr "Zaslali jsme email na adresu, kterou jste nám dodali, s odkazem na obnovu vášho hesla."
@@ -885,7 +906,7 @@ msgid "Blog banner"
msgstr "Blog banner" msgstr "Blog banner"
msgid "Custom theme" msgid "Custom theme"
msgstr "" msgstr "Vlastní vzhled"
msgid "Update blog" msgid "Update blog"
msgstr "Aktualizovat blog" msgstr "Aktualizovat blog"
+63 -39
View File
@@ -17,6 +17,10 @@ msgstr ""
"X-Crowdin-Language: cy\n" "X-Crowdin-Language: cy\n"
"X-Crowdin-File: /master/po/plume/plume.pot\n" "X-Crowdin-File: /master/po/plume/plume.pot\n"
# src/template_utils.rs:105
msgid "Someone"
msgstr ""
# src/template_utils.rs:68 # src/template_utils.rs:68
msgid "{0} commented on your article." msgid "{0} commented on your article."
msgstr "" msgstr ""
@@ -106,6 +110,24 @@ msgstr ""
msgid "Your comment has been deleted." msgid "Your comment has been deleted."
msgstr "" msgstr ""
# src/routes/user.rs:526
msgid "Registrations are closed on this instance."
msgstr ""
# src/routes/email_signups.rs:132
msgid "User registration"
msgstr ""
# src/routes/email_signups.rs:133
msgid "Here is the link for registration: {0}"
msgstr ""
# src/routes/user.rs:549
msgid ""
"Your account has been created. Now you just need to log in, before you can "
"use it."
msgstr ""
# src/routes/instance.rs:120 # src/routes/instance.rs:120
msgid "Instance settings have been saved." msgid "Instance settings have been saved."
msgstr "" msgstr ""
@@ -277,14 +299,49 @@ msgstr ""
msgid "You can't delete someone else's account." msgid "You can't delete someone else's account."
msgstr "" msgstr ""
# src/routes/user.rs:526 msgid "Create your account"
msgid "Registrations are closed on this instance." msgstr ""
msgid "Create an account"
msgstr ""
# src/template_utils.rs:217
msgid "Email"
msgstr ""
msgid "Email confirmation"
msgstr "" msgstr ""
# src/routes/user.rs:549
msgid "" msgid ""
"Your account has been created. Now you just need to log in, before you can " "An email will be sent to provided email. You can continue signing-up via the "
"use it." "email."
msgstr ""
msgid ""
"Apologies, but registrations are closed on this particular instance. You "
"can, however, find a different one."
msgstr ""
msgid "Registration"
msgstr ""
msgid "Check your inbox!"
msgstr ""
msgid ""
"We sent a mail to the address you gave us, with a link for registration."
msgstr ""
# src/template_utils.rs:217
msgid "Username"
msgstr ""
# src/template_utils.rs:217
msgid "Password"
msgstr ""
# src/template_utils.rs:217
msgid "Password confirmation"
msgstr "" msgstr ""
msgid "Media upload" msgid "Media upload"
@@ -426,33 +483,6 @@ msgstr ""
msgid "Subscriptions" msgid "Subscriptions"
msgstr "" msgstr ""
msgid "Create your account"
msgstr ""
msgid "Create an account"
msgstr ""
# src/template_utils.rs:217
msgid "Username"
msgstr ""
# src/template_utils.rs:217
msgid "Email"
msgstr ""
# src/template_utils.rs:217
msgid "Password"
msgstr ""
# src/template_utils.rs:217
msgid "Password confirmation"
msgstr ""
msgid ""
"Apologies, but registrations are closed on this particular instance. You "
"can, however, find a different one."
msgstr ""
msgid "{0}'s subscriptions" msgid "{0}'s subscriptions"
msgstr "" msgstr ""
@@ -715,7 +745,7 @@ msgstr ""
msgid "Welcome to {}" msgid "Welcome to {}"
msgstr "" msgstr ""
msgid "View all" msgid "Nothing to see here yet."
msgstr "" msgstr ""
msgid "About {0}" msgid "About {0}"
@@ -890,9 +920,6 @@ msgstr ""
msgid "Update password" msgid "Update password"
msgstr "" msgstr ""
msgid "Check your inbox!"
msgstr ""
msgid "" msgid ""
"We sent a mail to the address you gave us, with a link to reset your " "We sent a mail to the address you gave us, with a link to reset your "
"password." "password."
@@ -964,9 +991,6 @@ msgstr[5] ""
msgid "No posts to see here yet." msgid "No posts to see here yet."
msgstr "" msgstr ""
msgid "Nothing to see here yet."
msgstr ""
msgid "None" msgid "None"
msgstr "" msgstr ""
+98 -77
View File
@@ -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: 2021-05-05 18:31\n" "PO-Revision-Date: 2022-01-12 01:28\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Danish\n" "Language-Team: Danish\n"
"Language: da_DK\n" "Language: da_DK\n"
@@ -18,86 +18,90 @@ msgstr ""
"X-Crowdin-File-ID: 8\n" "X-Crowdin-File-ID: 8\n"
# src/template_utils.rs:105 # src/template_utils.rs:105
msgid "{0} commented on your article." msgid "Someone"
msgstr ""
# src/template_utils.rs:106
msgid "{0} is subscribed to you."
msgstr "" msgstr ""
# src/template_utils.rs:107 # src/template_utils.rs:107
msgid "{0} liked your article." msgid "{0} commented on your article."
msgstr "" msgstr ""
# src/template_utils.rs:108 # src/template_utils.rs:108
msgid "{0} mentioned you." msgid "{0} is subscribed to you."
msgstr "" msgstr ""
# src/template_utils.rs:109 # src/template_utils.rs:109
msgid "{0} liked your article."
msgstr ""
# src/template_utils.rs:110
msgid "{0} mentioned you."
msgstr ""
# src/template_utils.rs:111
msgid "{0} boosted your article." msgid "{0} boosted your article."
msgstr "" msgstr ""
# src/template_utils.rs:116 # src/template_utils.rs:118
msgid "Your feed" msgid "Your feed"
msgstr "" msgstr ""
# src/template_utils.rs:117 # src/template_utils.rs:119
msgid "Local feed" msgid "Local feed"
msgstr "" msgstr ""
# src/template_utils.rs:118 # src/template_utils.rs:120
msgid "Federated feed" msgid "Federated feed"
msgstr "" msgstr ""
# src/template_utils.rs:154 # src/template_utils.rs:156
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "" msgstr ""
# src/template_utils.rs:198 # src/template_utils.rs:200
msgid "Previous page" msgid "Previous page"
msgstr "" msgstr ""
# src/template_utils.rs:209 # src/template_utils.rs:211
msgid "Next page" msgid "Next page"
msgstr "" msgstr ""
# src/template_utils.rs:363 # src/template_utils.rs:365
msgid "Optional" msgid "Optional"
msgstr "" msgstr ""
# src/routes/blogs.rs:67 # src/routes/blogs.rs:68
msgid "To create a new blog, you need to be logged in" msgid "To create a new blog, you need to be logged in"
msgstr "" msgstr ""
# src/routes/blogs.rs:109 # src/routes/blogs.rs:110
msgid "A blog with the same name already exists." msgid "A blog with the same name already exists."
msgstr "" msgstr ""
# src/routes/blogs.rs:147 # src/routes/blogs.rs:148
msgid "Your blog was successfully created!" msgid "Your blog was successfully created!"
msgstr "" msgstr ""
# src/routes/blogs.rs:165 # src/routes/blogs.rs:166
msgid "Your blog was deleted." msgid "Your blog was deleted."
msgstr "" msgstr ""
# src/routes/blogs.rs:173 # src/routes/blogs.rs:174
msgid "You are not allowed to delete this blog." msgid "You are not allowed to delete this blog."
msgstr "" msgstr ""
# src/routes/blogs.rs:223 # src/routes/blogs.rs:224
msgid "You are not allowed to edit this blog." msgid "You are not allowed to edit this blog."
msgstr "" msgstr ""
# src/routes/blogs.rs:279 # src/routes/blogs.rs:280
msgid "You can't use this media as a blog icon." msgid "You can't use this media as a blog icon."
msgstr "" msgstr ""
# src/routes/blogs.rs:297 # src/routes/blogs.rs:298
msgid "You can't use this media as a blog banner." msgid "You can't use this media as a blog banner."
msgstr "" msgstr ""
# src/routes/blogs.rs:331 # src/routes/blogs.rs:332
msgid "Your blog information have been updated." msgid "Your blog information have been updated."
msgstr "" msgstr ""
@@ -109,6 +113,22 @@ msgstr ""
msgid "Your comment has been deleted." msgid "Your comment has been deleted."
msgstr "" msgstr ""
# src/routes/email_signups.rs:82
msgid "Registrations are closed on this instance."
msgstr ""
# src/routes/email_signups.rs:119
msgid "User registration"
msgstr ""
# src/routes/email_signups.rs:120
msgid "Here is the link for registration: {0}"
msgstr ""
# src/routes/email_signups.rs:219
msgid "Your account has been created. Now you just need to log in, before you can use it."
msgstr ""
# src/routes/instance.rs:117 # src/routes/instance.rs:117
msgid "Instance settings have been saved." msgid "Instance settings have been saved."
msgstr "" msgstr ""
@@ -149,19 +169,19 @@ msgstr ""
msgid "To like a post, you need to be logged in" msgid "To like a post, you need to be logged in"
msgstr "" msgstr ""
# src/routes/medias.rs:158 # src/routes/medias.rs:153
msgid "Your media have been deleted." msgid "Your media have been deleted."
msgstr "" msgstr ""
# src/routes/medias.rs:163 # src/routes/medias.rs:158
msgid "You are not allowed to delete this media." msgid "You are not allowed to delete this media."
msgstr "" msgstr ""
# src/routes/medias.rs:180 # src/routes/medias.rs:175
msgid "Your avatar has been updated." msgid "Your avatar has been updated."
msgstr "" msgstr ""
# src/routes/medias.rs:185 # src/routes/medias.rs:180
msgid "You are not allowed to use this media." msgid "You are not allowed to use this media."
msgstr "" msgstr ""
@@ -169,55 +189,55 @@ msgstr ""
msgid "To see your notifications, you need to be logged in" msgid "To see your notifications, you need to be logged in"
msgstr "" msgstr ""
# src/routes/posts.rs:55 # src/routes/posts.rs:56
msgid "This post isn't published yet." msgid "This post isn't published yet."
msgstr "" msgstr ""
# src/routes/posts.rs:125 # src/routes/posts.rs:126
msgid "To write a new post, you need to be logged in" msgid "To write a new post, you need to be logged in"
msgstr "" msgstr ""
# src/routes/posts.rs:146 # src/routes/posts.rs:147
msgid "You are not an author of this blog." msgid "You are not an author of this blog."
msgstr "" msgstr ""
# src/routes/posts.rs:153 # src/routes/posts.rs:154
msgid "New post" msgid "New post"
msgstr "" msgstr ""
# src/routes/posts.rs:198 # src/routes/posts.rs:199
msgid "Edit {0}" msgid "Edit {0}"
msgstr "" msgstr ""
# src/routes/posts.rs:267 # src/routes/posts.rs:268
msgid "You are not allowed to publish on this blog." msgid "You are not allowed to publish on this blog."
msgstr "" msgstr ""
# src/routes/posts.rs:367 # src/routes/posts.rs:368
msgid "Your article has been updated." msgid "Your article has been updated."
msgstr "" msgstr ""
# src/routes/posts.rs:556 # src/routes/posts.rs:557
msgid "Your article has been saved." msgid "Your article has been saved."
msgstr "" msgstr ""
# src/routes/posts.rs:563 # src/routes/posts.rs:564
msgid "New article" msgid "New article"
msgstr "" msgstr ""
# src/routes/posts.rs:601 # src/routes/posts.rs:602
msgid "You are not allowed to delete this article." msgid "You are not allowed to delete this article."
msgstr "" msgstr ""
# src/routes/posts.rs:625 # src/routes/posts.rs:626
msgid "Your article has been deleted." msgid "Your article has been deleted."
msgstr "" msgstr ""
# src/routes/posts.rs:630 # src/routes/posts.rs:631
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?" msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
msgstr "" msgstr ""
# src/routes/posts.rs:672 # src/routes/posts.rs:673
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct." msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
msgstr "" msgstr ""
@@ -245,44 +265,69 @@ msgstr ""
msgid "Your password was successfully reset." msgid "Your password was successfully reset."
msgstr "" msgstr ""
# src/routes/user.rs:74 # src/routes/user.rs:87
msgid "To access your dashboard, you need to be logged in" msgid "To access your dashboard, you need to be logged in"
msgstr "" msgstr ""
# src/routes/user.rs:96 # src/routes/user.rs:109
msgid "You are no longer following {}." msgid "You are no longer following {}."
msgstr "" msgstr ""
# src/routes/user.rs:113 # src/routes/user.rs:126
msgid "You are now following {}." msgid "You are now following {}."
msgstr "" msgstr ""
# src/routes/user.rs:190 # src/routes/user.rs:203
msgid "To subscribe to someone, you need to be logged in" msgid "To subscribe to someone, you need to be logged in"
msgstr "" msgstr ""
# src/routes/user.rs:299 # src/routes/user.rs:323
msgid "To edit your profile, you need to be logged in" msgid "To edit your profile, you need to be logged in"
msgstr "" msgstr ""
# src/routes/user.rs:345 # src/routes/user.rs:369
msgid "Your profile has been updated." msgid "Your profile has been updated."
msgstr "" msgstr ""
# src/routes/user.rs:373 # src/routes/user.rs:397
msgid "Your account has been deleted." msgid "Your account has been deleted."
msgstr "" msgstr ""
# src/routes/user.rs:379 # src/routes/user.rs:403
msgid "You can't delete someone else's account." msgid "You can't delete someone else's account."
msgstr "" msgstr ""
# src/routes/user.rs:463 msgid "Create your account"
msgid "Registrations are closed on this instance."
msgstr "" msgstr ""
# src/routes/user.rs:486 msgid "Create an account"
msgid "Your account has been created. Now you just need to log in, before you can use it." msgstr ""
msgid "Email"
msgstr ""
msgid "Email confirmation"
msgstr ""
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr ""
msgid "Registration"
msgstr ""
msgid "Check your inbox!"
msgstr ""
msgid "We sent a mail to the address you gave us, with a link for registration."
msgstr ""
msgid "Username"
msgstr ""
msgid "Password"
msgstr ""
msgid "Password confirmation"
msgstr "" msgstr ""
msgid "Media upload" msgid "Media upload"
@@ -423,27 +468,6 @@ msgstr ""
msgid "Subscriptions" msgid "Subscriptions"
msgstr "" msgstr ""
msgid "Create your account"
msgstr ""
msgid "Create an account"
msgstr ""
msgid "Username"
msgstr ""
msgid "Email"
msgstr ""
msgid "Password"
msgstr ""
msgid "Password confirmation"
msgstr ""
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr ""
msgid "{0}'s subscriptions" msgid "{0}'s subscriptions"
msgstr "" msgstr ""
@@ -841,9 +865,6 @@ msgstr ""
msgid "Update password" msgid "Update password"
msgstr "" msgstr ""
msgid "Check your inbox!"
msgstr ""
msgid "We sent a mail to the address you gave us, with a link to reset your password." msgid "We sent a mail to the address you gave us, with a link to reset your password."
msgstr "" msgstr ""
+97 -76
View File
@@ -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: 2021-12-11 15:00\n" "PO-Revision-Date: 2022-01-26 13:16\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Language: de_DE\n" "Language: de_DE\n"
@@ -18,86 +18,90 @@ msgstr ""
"X-Crowdin-File-ID: 8\n" "X-Crowdin-File-ID: 8\n"
# src/template_utils.rs:105 # src/template_utils.rs:105
msgid "Someone"
msgstr "Jemand"
# src/template_utils.rs:107
msgid "{0} commented on your article." msgid "{0} commented on your article."
msgstr "{0} hat deinen Artikel kommentiert." msgstr "{0} hat deinen Artikel kommentiert."
# src/template_utils.rs:106 # src/template_utils.rs:108
msgid "{0} is subscribed to you." msgid "{0} is subscribed to you."
msgstr "{0} hat dich abonniert." msgstr "{0} hat dich abonniert."
# src/template_utils.rs:107 # src/template_utils.rs:109
msgid "{0} liked your article." msgid "{0} liked your article."
msgstr "{0} gefällt Ihr Artikel." msgstr "{0} gefällt Ihr Artikel."
# src/template_utils.rs:108 # src/template_utils.rs:110
msgid "{0} mentioned you." msgid "{0} mentioned you."
msgstr "{0} hat dich erwähnt." msgstr "{0} hat dich erwähnt."
# src/template_utils.rs:109 # src/template_utils.rs:111
msgid "{0} boosted your article." msgid "{0} boosted your article."
msgstr "{0} hat deinen Artikel geboosted." msgstr "{0} hat deinen Artikel geboosted."
# src/template_utils.rs:116 # src/template_utils.rs:118
msgid "Your feed" msgid "Your feed"
msgstr "Dein Feed" msgstr "Dein Feed"
# src/template_utils.rs:117 # src/template_utils.rs:119
msgid "Local feed" msgid "Local feed"
msgstr "Lokaler Feed" msgstr "Lokaler Feed"
# src/template_utils.rs:118 # src/template_utils.rs:120
msgid "Federated feed" msgid "Federated feed"
msgstr "Föderierter Feed" msgstr "Föderierter Feed"
# src/template_utils.rs:154 # src/template_utils.rs:156
msgid "{0}'s avatar" msgid "{0}'s avatar"
msgstr "{0}'s Profilbild" msgstr "{0}'s Profilbild"
# src/template_utils.rs:198 # src/template_utils.rs:200
msgid "Previous page" msgid "Previous page"
msgstr "Vorherige Seite" msgstr "Vorherige Seite"
# src/template_utils.rs:209 # src/template_utils.rs:211
msgid "Next page" msgid "Next page"
msgstr "Nächste Seite" msgstr "Nächste Seite"
# src/template_utils.rs:363 # src/template_utils.rs:365
msgid "Optional" msgid "Optional"
msgstr "Optional" msgstr "Optional"
# src/routes/blogs.rs:67 # src/routes/blogs.rs:68
msgid "To create a new blog, you need to be logged in" msgid "To create a new blog, you need to be logged in"
msgstr "Du musst angemeldet sein, um einen Blog zu erstellen" msgstr "Du musst angemeldet sein, um einen Blog zu erstellen"
# src/routes/blogs.rs:109 # src/routes/blogs.rs:110
msgid "A blog with the same name already exists." msgid "A blog with the same name already exists."
msgstr "Es existiert bereits ein Blog mit diesem Namen." msgstr "Es existiert bereits ein Blog mit diesem Namen."
# src/routes/blogs.rs:147 # src/routes/blogs.rs:148
msgid "Your blog was successfully created!" msgid "Your blog was successfully created!"
msgstr "Dein Blog wurde erfolgreich erstellt!" msgstr "Dein Blog wurde erfolgreich erstellt!"
# src/routes/blogs.rs:165 # src/routes/blogs.rs:166
msgid "Your blog was deleted." msgid "Your blog was deleted."
msgstr "Dein Blog wurde gelöscht." msgstr "Dein Blog wurde gelöscht."
# src/routes/blogs.rs:173 # src/routes/blogs.rs:174
msgid "You are not allowed to delete this blog." msgid "You are not allowed to delete this blog."
msgstr "Du bist nicht berechtigt, diesen Blog zu löschen." msgstr "Du bist nicht berechtigt, diesen Blog zu löschen."
# src/routes/blogs.rs:223 # src/routes/blogs.rs:224
msgid "You are not allowed to edit this blog." msgid "You are not allowed to edit this blog."
msgstr "Du bist nicht berechtigt, diesen Blog zu bearbeiten." msgstr "Du bist nicht berechtigt, diesen Blog zu bearbeiten."
# src/routes/blogs.rs:279 # src/routes/blogs.rs:280
msgid "You can't use this media as a blog icon." msgid "You can't use this media as a blog icon."
msgstr "Du kannst dieses Medium nicht als Blog-Symbol verwenden." msgstr "Du kannst dieses Medium nicht als Blog-Symbol verwenden."
# src/routes/blogs.rs:297 # src/routes/blogs.rs:298
msgid "You can't use this media as a blog banner." msgid "You can't use this media as a blog banner."
msgstr "Du kannst diese Datei nicht als Blog-Banner verwenden." msgstr "Du kannst diese Datei nicht als Blog-Banner verwenden."
# src/routes/blogs.rs:331 # src/routes/blogs.rs:332
msgid "Your blog information have been updated." msgid "Your blog information have been updated."
msgstr "Informationen des Blog wurden aktualisiert." msgstr "Informationen des Blog wurden aktualisiert."
@@ -109,6 +113,22 @@ msgstr "Dein Kommentar wurde veröffentlicht."
msgid "Your comment has been deleted." msgid "Your comment has been deleted."
msgstr "Dein Kommentar wurde gelöscht." msgstr "Dein Kommentar wurde gelöscht."
# src/routes/email_signups.rs:82
msgid "Registrations are closed on this instance."
msgstr "Anmeldungen sind auf dieser Instanz aktuell nicht möglich."
# src/routes/email_signups.rs:119
msgid "User registration"
msgstr "Benutzerregistrierung"
# src/routes/email_signups.rs:120
msgid "Here is the link for registration: {0}"
msgstr "Dies ist der Link für die Anmeldung: {0}"
# src/routes/email_signups.rs:219
msgid "Your account has been created. Now you just need to log in, before you can use it."
msgstr "Dein Konto wurde erstellt. Jetzt musst du dich nur noch anmelden, um es nutzen zu können."
# src/routes/instance.rs:117 # src/routes/instance.rs:117
msgid "Instance settings have been saved." msgid "Instance settings have been saved."
msgstr "Die Instanzeinstellungen wurden gespeichert." msgstr "Die Instanzeinstellungen wurden gespeichert."
@@ -149,19 +169,19 @@ msgstr "Fertig"
msgid "To like a post, you need to be logged in" msgid "To like a post, you need to be logged in"
msgstr "Um einen Beitrag zu liken, musst du angemeldet sein" msgstr "Um einen Beitrag zu liken, musst du angemeldet sein"
# src/routes/medias.rs:158 # src/routes/medias.rs:153
msgid "Your media have been deleted." msgid "Your media have been deleted."
msgstr "Deine Datei wurde gelöscht." msgstr "Deine Datei wurde gelöscht."
# src/routes/medias.rs:163 # src/routes/medias.rs:158
msgid "You are not allowed to delete this media." msgid "You are not allowed to delete this media."
msgstr "Dir fehlt die Berechtigung, diese Datei zu löschen." msgstr "Dir fehlt die Berechtigung, diese Datei zu löschen."
# src/routes/medias.rs:180 # src/routes/medias.rs:175
msgid "Your avatar has been updated." msgid "Your avatar has been updated."
msgstr "Dein Benutzerbild wurde aktualisiert." msgstr "Dein Benutzerbild wurde aktualisiert."
# src/routes/medias.rs:185 # src/routes/medias.rs:180
msgid "You are not allowed to use this media." msgid "You are not allowed to use this media."
msgstr "Dir fehlt die Berechtigung, um diese Datei zu nutzen." msgstr "Dir fehlt die Berechtigung, um diese Datei zu nutzen."
@@ -169,55 +189,55 @@ msgstr "Dir fehlt die Berechtigung, um diese Datei zu nutzen."
msgid "To see your notifications, you need to be logged in" msgid "To see your notifications, you need to be logged in"
msgstr "Um deine Benachrichtigungen zu sehen, musst du angemeldet sein" msgstr "Um deine Benachrichtigungen zu sehen, musst du angemeldet sein"
# src/routes/posts.rs:55 # src/routes/posts.rs:56
msgid "This post isn't published yet." msgid "This post isn't published yet."
msgstr "Dieser Beitrag wurde noch nicht veröffentlicht." msgstr "Dieser Beitrag wurde noch nicht veröffentlicht."
# src/routes/posts.rs:125 # src/routes/posts.rs:126
msgid "To write a new post, you need to be logged in" msgid "To write a new post, you need to be logged in"
msgstr "Um einen neuen Beitrag zu schreiben, musst du angemeldet sein" msgstr "Um einen neuen Beitrag zu schreiben, musst du angemeldet sein"
# src/routes/posts.rs:146 # src/routes/posts.rs:147
msgid "You are not an author of this blog." msgid "You are not an author of this blog."
msgstr "Du bist kein Autor dieses Blogs." msgstr "Du bist kein Autor dieses Blogs."
# src/routes/posts.rs:153 # src/routes/posts.rs:154
msgid "New post" msgid "New post"
msgstr "Neuer Beitrag" msgstr "Neuer Beitrag"
# src/routes/posts.rs:198 # src/routes/posts.rs:199
msgid "Edit {0}" msgid "Edit {0}"
msgstr "{0} bearbeiten" msgstr "{0} bearbeiten"
# src/routes/posts.rs:267 # src/routes/posts.rs:268
msgid "You are not allowed to publish on this blog." msgid "You are not allowed to publish on this blog."
msgstr "Dir fehlt die Berechtigung, in diesem Blog zu veröffentlichen." msgstr "Dir fehlt die Berechtigung, in diesem Blog zu veröffentlichen."
# src/routes/posts.rs:367 # src/routes/posts.rs:368
msgid "Your article has been updated." msgid "Your article has been updated."
msgstr "Dein Artikel wurde aktualisiert." msgstr "Dein Artikel wurde aktualisiert."
# src/routes/posts.rs:556 # src/routes/posts.rs:557
msgid "Your article has been saved." msgid "Your article has been saved."
msgstr "Dein Artikel wurde gespeichert." msgstr "Dein Artikel wurde gespeichert."
# src/routes/posts.rs:563 # src/routes/posts.rs:564
msgid "New article" msgid "New article"
msgstr "Neuer Artikel" msgstr "Neuer Artikel"
# src/routes/posts.rs:601 # src/routes/posts.rs:602
msgid "You are not allowed to delete this article." msgid "You are not allowed to delete this article."
msgstr "Dir fehlt die Berechtigung, diesen Artikel zu löschen." msgstr "Dir fehlt die Berechtigung, diesen Artikel zu löschen."
# src/routes/posts.rs:625 # src/routes/posts.rs:626
msgid "Your article has been deleted." msgid "Your article has been deleted."
msgstr "Dein Artikel wurde gelöscht." msgstr "Dein Artikel wurde gelöscht."
# src/routes/posts.rs:630 # src/routes/posts.rs:631
msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?" msgid "It looks like the article you tried to delete doesn't exist. Maybe it is already gone?"
msgstr "Möglicherweise ist der zu löschende Artikel nicht (mehr) vorhanden. Wurde er vielleicht schon entfernt?" msgstr "Möglicherweise ist der zu löschende Artikel nicht (mehr) vorhanden. Wurde er vielleicht schon entfernt?"
# src/routes/posts.rs:672 # src/routes/posts.rs:673
msgid "Couldn't obtain enough information about your account. Please make sure your username is correct." msgid "Couldn't obtain enough information about your account. Please make sure your username is correct."
msgstr "Wir konnten nicht genug Informationen über dein Konto finden. Bitte stelle sicher, dass dein Benutzername richtig ist." msgstr "Wir konnten nicht genug Informationen über dein Konto finden. Bitte stelle sicher, dass dein Benutzername richtig ist."
@@ -245,45 +265,70 @@ msgstr "Hier der Link, um das Passwort zurückzusetzen: {0}"
msgid "Your password was successfully reset." msgid "Your password was successfully reset."
msgstr "Dein Passwort wurde erfolgreich zurückgesetzt." msgstr "Dein Passwort wurde erfolgreich zurückgesetzt."
# src/routes/user.rs:74 # src/routes/user.rs:87
msgid "To access your dashboard, you need to be logged in" msgid "To access your dashboard, you need to be logged in"
msgstr "Um auf dein Dashboard zuzugreifen, musst du angemeldet sein" msgstr "Um auf dein Dashboard zuzugreifen, musst du angemeldet sein"
# src/routes/user.rs:96 # src/routes/user.rs:109
msgid "You are no longer following {}." msgid "You are no longer following {}."
msgstr "Du folgst {} nun nicht mehr." msgstr "Du folgst {} nun nicht mehr."
# src/routes/user.rs:113 # src/routes/user.rs:126
msgid "You are now following {}." msgid "You are now following {}."
msgstr "Du folgst nun {}." msgstr "Du folgst nun {}."
# src/routes/user.rs:190 # src/routes/user.rs:203
msgid "To subscribe to someone, you need to be logged in" msgid "To subscribe to someone, you need to be logged in"
msgstr "Um jemanden zu abonnieren, musst du angemeldet sein" msgstr "Um jemanden zu abonnieren, musst du angemeldet sein"
# src/routes/user.rs:299 # src/routes/user.rs:323
msgid "To edit your profile, you need to be logged in" msgid "To edit your profile, you need to be logged in"
msgstr "Um dein Profil zu bearbeiten, musst du angemeldet sein" msgstr "Um dein Profil zu bearbeiten, musst du angemeldet sein"
# src/routes/user.rs:345 # src/routes/user.rs:369
msgid "Your profile has been updated." msgid "Your profile has been updated."
msgstr "Dein Profil wurde aktualisiert." msgstr "Dein Profil wurde aktualisiert."
# src/routes/user.rs:373 # src/routes/user.rs:397
msgid "Your account has been deleted." msgid "Your account has been deleted."
msgstr "Dein Benutzerkonto wurde gelöscht." msgstr "Dein Benutzerkonto wurde gelöscht."
# src/routes/user.rs:379 # src/routes/user.rs:403
msgid "You can't delete someone else's account." msgid "You can't delete someone else's account."
msgstr "Dir fehlt die Berechtigung, das Konto eines anderen zu löschen." msgstr "Dir fehlt die Berechtigung, das Konto eines anderen zu löschen."
# src/routes/user.rs:463 msgid "Create your account"
msgid "Registrations are closed on this instance." msgstr "Eigenen Account erstellen"
msgstr "Anmeldungen sind auf dieser Instanz aktuell nicht möglich."
# src/routes/user.rs:486 msgid "Create an account"
msgid "Your account has been created. Now you just need to log in, before you can use it." msgstr "Konto erstellen"
msgstr "Dein Konto wurde erstellt. Jetzt musst du dich nur noch anmelden, um es nutzen zu können."
msgid "Email"
msgstr "E-Mail-Adresse"
msgid "Email confirmation"
msgstr "E-Mail-Bestätigung"
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "Entschuldigung, Registrierungen sind auf dieser Instanz geschlossen. Du kannst jedoch eine andere finden."
msgid "Registration"
msgstr "Registrierung"
msgid "Check your inbox!"
msgstr "Posteingang prüfen!"
msgid "We sent a mail to the address you gave us, with a link for registration."
msgstr "Wir haben eine E-Mail mit einem Link zur Registrierung an die von Ihnen angegebene Adresse gesendet."
msgid "Username"
msgstr "Benutzername"
msgid "Password"
msgstr "Passwort"
msgid "Password confirmation"
msgstr "Passwort bestätigen"
msgid "Media upload" msgid "Media upload"
msgstr "Hochladen von Mediendateien" msgstr "Hochladen von Mediendateien"
@@ -423,27 +468,6 @@ msgstr "Abonnenten"
msgid "Subscriptions" msgid "Subscriptions"
msgstr "Abonnement" msgstr "Abonnement"
msgid "Create your account"
msgstr "Eigenen Account erstellen"
msgid "Create an account"
msgstr "Konto erstellen"
msgid "Username"
msgstr "Benutzername"
msgid "Email"
msgstr "E-Mail-Adresse"
msgid "Password"
msgstr "Passwort"
msgid "Password confirmation"
msgstr "Passwort bestätigen"
msgid "Apologies, but registrations are closed on this particular instance. You can, however, find a different one."
msgstr "Entschuldigung, Registrierungen sind auf dieser Instanz geschlossen. Du kannst jedoch eine andere finden."
msgid "{0}'s subscriptions" msgid "{0}'s subscriptions"
msgstr "{0}'s Abonnements" msgstr "{0}'s Abonnements"
@@ -841,9 +865,6 @@ msgstr "Bestätigung"
msgid "Update password" msgid "Update password"
msgstr "Passwort aktualisieren" msgstr "Passwort aktualisieren"
msgid "Check your inbox!"
msgstr "Posteingang prüfen!"
msgid "We sent a mail to the address you gave us, with a link to reset your password." msgid "We sent a mail to the address you gave us, with a link to reset your password."
msgstr "Wir haben eine Mail an die von dir angegebene Adresse gesendet, mit einem Link, um dein Passwort zurückzusetzen." msgstr "Wir haben eine Mail an die von dir angegebene Adresse gesendet, mit einem Link, um dein Passwort zurückzusetzen."

Some files were not shown because too many files have changed in this diff Show More