Compare commits
581 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a824f06c3 | |||
| eec09d79fe | |||
| 79b639c3e6 | |||
| efef208f53 | |||
| 27e0f755f6 | |||
| a9d7aae5d6 | |||
| 42e584a363 | |||
| 8c37ea3ec3 | |||
| 3c14fa0058 | |||
| ab94cca210 | |||
| ea62388985 | |||
| a9219efee4 | |||
| 776ed058c7 | |||
| aa3e196b8f | |||
| 52cb7270a9 | |||
| 66376afb36 | |||
| 96860be1be | |||
| 3bf61efc34 | |||
| d95549f58b | |||
| bf24e4878a | |||
| 9ae231fcef | |||
| c32acb2fcf | |||
| 770c77ee81 | |||
| aa3e4d7cf8 | |||
| 156a875f02 | |||
| cfed02bbcf | |||
| 4d3db9af73 | |||
| c1c606bc86 | |||
| f401949037 | |||
| e8dc0942e5 | |||
| 9fbafd8e79 | |||
| b9ea83a602 | |||
| 2ada5a83af | |||
| ec25599d1f | |||
| 57551610e2 | |||
| 8948b7acc1 | |||
| ccf7ff2bc9 | |||
| 39de967141 | |||
| 118cfd7166 | |||
| c2fd4ab3a5 | |||
| 70b5bee00f | |||
| de605deb1e | |||
| 116974f811 | |||
| c57f36ccca | |||
| 9def0355aa | |||
| 4e833c2061 | |||
| 5871ed7301 | |||
| 97632fdbfe | |||
| 1f8da7e63d | |||
| 76ca7c1462 | |||
| f06f444a13 | |||
| 10dfecf45c | |||
| a7b899817a | |||
| e0258003b9 | |||
| 2326eb77cd | |||
| 504d41d887 | |||
| 5a7d5e8099 | |||
| 74a1daac8c | |||
| 1f855601ea | |||
| f22c4d5c78 | |||
| ce4b216722 | |||
| 9016995d92 | |||
| 853a1db028 | |||
| 712ee30a1f | |||
| 9e5f9255d1 | |||
| 2e35441483 | |||
| 5c74f598d8 | |||
| 5d711dc47c | |||
| 9ae3057106 | |||
| b7ea154e51 | |||
| 692e6b1c82 | |||
| 528f1bac48 | |||
| 35aa2374c4 | |||
| 3eb7662aef | |||
| de4fcaee93 | |||
| 812fd3d956 | |||
| 5d3b480790 | |||
| 2f1801acae | |||
| 0404528908 | |||
| 4529b929d8 | |||
| 889decc720 | |||
| db0f1a3c46 | |||
| ef57ef91f0 | |||
| 073b72c9ed | |||
| 45a6744d4d | |||
| 4d19861a25 | |||
| f5906cacf3 | |||
| 03ba77a577 | |||
| 0fc7372781 | |||
| 6c2846980a | |||
| 0685c59bf3 | |||
| 5f629195f8 | |||
| 13eeedb620 | |||
| a076c132ca | |||
| a76e0dfe5b | |||
| 8faac20977 | |||
| b04edfa05e | |||
| 1f62bf27f8 | |||
| 1e0d1fb97a | |||
| 19d30c12d1 | |||
| 384474930c | |||
| 4cb64e0a8c | |||
| cd2a2df48d | |||
| cbf960500b | |||
| 3d434f1923 | |||
| 52022fb597 | |||
| d96940c848 | |||
| b17884681d | |||
| 78a001ac89 | |||
| 9b04fb96e6 | |||
| d23002b817 | |||
| 6282b98b03 | |||
| d75600ba14 | |||
| e6ea302319 | |||
| df005a28f8 | |||
| 15134eed60 | |||
| 7dd56a71e3 | |||
| 06d2f68ecd | |||
| 9a640b3438 | |||
| 9791607793 | |||
| ccd3c8a3f2 | |||
| 6bbadc78b0 | |||
| e1673787b4 | |||
| 1dd176dd80 | |||
| 5c59687cb8 | |||
| a24e3c46e6 | |||
| 267fecba66 | |||
| fc99d2b7a0 | |||
| 1b32fa1e34 | |||
| ce42524273 | |||
| 595fa05660 | |||
| f8a0dff526 | |||
| 06d216c7ed | |||
| f44bca30f4 | |||
| e4180b3b38 | |||
| 992a482b96 | |||
| 0ad845e0f7 | |||
| ee97213c90 | |||
| 6d919da049 | |||
| 6c615d01ad | |||
| f8870af9fe | |||
| 2fe2505a01 | |||
| e41fa353e4 | |||
| effdc44943 | |||
| fd341bdb22 | |||
| 68c794c54b | |||
| 7b3b00be23 | |||
| 41ccacc5d3 | |||
| 4ef9350ce7 | |||
| 5d08ff6c3b | |||
| 01dca62ce5 | |||
| 6ab1ecd57b | |||
| b13444895f | |||
| 771c157fe5 | |||
| b5e1076b0e | |||
| 6cc43c2420 | |||
| f365041a45 | |||
| ae9c9262f7 | |||
| 40ce515e6c | |||
| bc96af7f5f | |||
| 811c20c8fb | |||
| 4b4c22cf8a | |||
| 0524b0b153 | |||
| cd6c57b9c5 | |||
| f608f7a4d6 | |||
| 803680186b | |||
| 68a01d5f9b | |||
| 5b3a472b66 | |||
| 2a85f775e9 | |||
| a958300a58 | |||
| a8be31b177 | |||
| 6cd68ab8b0 | |||
| a589435f4f | |||
| 39b49c707e | |||
| c4bb1f771b | |||
| 28440271bb | |||
| 0ab7774e29 | |||
| 33afe9111e | |||
| d8a2e1925f | |||
| 2804f44a06 | |||
| 2165c286ae | |||
| 8cbf410faf | |||
| c521a81373 | |||
| 7ade0550c9 | |||
| 41bc2d6949 | |||
| de6e9c0e2e | |||
| 38ebc9ea41 | |||
| 8f976be998 | |||
| e5a2850105 | |||
| 85727c6d4c | |||
| 87247a23b3 | |||
| 61785364e3 | |||
| 76f688c967 | |||
| 05df3b89a1 | |||
| 4e42a34337 | |||
| 62372201e0 | |||
| 036913a828 | |||
| b2a889b9e4 | |||
| fae8338772 | |||
| 79b5d9a690 | |||
| 3e54d10981 | |||
| a1c3bfb646 | |||
| b2528c21ff | |||
| fcc9e1d81b | |||
| 3093f713ef | |||
| 4ea29d29a0 | |||
| 6b8d90d8b6 | |||
| bd3e6a5a91 | |||
| 46f4676efb | |||
| c814ac5681 | |||
| 0887399048 | |||
| f2a2bf2b23 | |||
| e2702a187b | |||
| d78a57ce47 | |||
| 10acbdd41f | |||
| 73009818f2 | |||
| fb5027becd | |||
| 86609b51fa | |||
| 44799e94fd | |||
| f14c307786 | |||
| 174624f5c1 | |||
| 5f91345d69 | |||
| 9ca975113c | |||
| 38a55857c6 | |||
| 9343d3a120 | |||
| c5656971c9 | |||
| ed55b66253 | |||
| 713ffb9506 | |||
| 9969e844ca | |||
| 0c61dca9ca | |||
| 957725fbf8 | |||
| 1f6361a9a2 | |||
| cf870971d1 | |||
| 08ac7227b5 | |||
| 88eb61c320 | |||
| 1c1dbd481a | |||
| 86b4f622ea | |||
| f854bc5838 | |||
| 489156f4a3 | |||
| 01e8b0bce8 | |||
| 9183d04e66 | |||
| 5e463e2cc9 | |||
| 6e2bff10f7 | |||
| 3e9d9a81b7 | |||
| 98e0754976 | |||
| da7870eeba | |||
| c1562f3868 | |||
| e0390cb105 | |||
| 32cd91cfb9 | |||
| 991dfccf3b | |||
| 16e012ba00 | |||
| 871618f45d | |||
| 680d321a2e | |||
| c37ff54857 | |||
| d4018d61d4 | |||
| 21a0059755 | |||
| 05f4c186f4 | |||
| 53512a6167 | |||
| 7cf7700ef7 | |||
| 216855d3a7 | |||
| 23f273e5e8 | |||
| 70949fad02 | |||
| 1f5ce8e504 | |||
| 2316d36e03 | |||
| 2b1ddc71ac | |||
| b9dac1a21a | |||
| 95fb5a3c71 | |||
| 75b43a738f | |||
| 5bd467c4c1 | |||
| 74d6dc5089 | |||
| 994a4dbb2d | |||
| 67996cc938 | |||
| f5e776c4d7 | |||
| e27fc47287 | |||
| 0ed91b89ff | |||
| 00862790a1 | |||
| ab6f39c192 | |||
| 4edc201c14 | |||
| a1a7acfe94 | |||
| 6b5a1d2130 | |||
| 85e35fdb5d | |||
| da9e13622c | |||
| 8f4dd8a57b | |||
| 78b0535063 | |||
| 6323c7aef8 | |||
| 7f0ad56d07 | |||
| e7eea3901f | |||
| 0979471e54 | |||
| 8d69051a61 | |||
| 55ca1345e1 | |||
| ad951ca842 | |||
| cb8e2e9294 | |||
| 038d65acaa | |||
| e392a89526 | |||
| d62f51665b | |||
| e42aa6fe8e | |||
| ab126563f3 | |||
| c1b9ebdae6 | |||
| d3e11c78d7 | |||
| 4ccfec8019 | |||
| bb5157637d | |||
| 456df3e535 | |||
| f0112850fa | |||
| a6a21d5dfa | |||
| 249fbbe891 | |||
| e925865767 | |||
| 28643fc2c2 | |||
| 3db10a09bb | |||
| a80a95d471 | |||
| e407d58ee9 | |||
| a6d839a766 | |||
| f3b67ab6c9 | |||
| 66f5628a27 | |||
| 4b3b5c1f40 | |||
| 3e687f3af0 | |||
| 119d3e4f6a | |||
| a21d66178e | |||
| 52967f3e47 | |||
| 29439f9d02 | |||
| bc72a4c2d1 | |||
| 3ded0e2166 | |||
| 27c10e5e5c | |||
| 816aefe72a | |||
| bff50f8e4c | |||
| be1c22815b | |||
| 08ab7ffd08 | |||
| 8709f6cf9f | |||
| 04cae95635 | |||
| 5d37b2534a | |||
| eafafdaadf | |||
| 6897b8fa58 | |||
| 16d3279d72 | |||
| 36c76c534d | |||
| 26f460be89 | |||
| b9fb13104a | |||
| 5cc411158f | |||
| ca69c93531 | |||
| 95cb7cc904 | |||
| 0a62fa46aa | |||
| 2e60410969 | |||
| a12d3a591b | |||
| 3cf7c67b6d | |||
| 2cf79f31b7 | |||
| 24cf941303 | |||
| 38cf9b5496 | |||
| b9607b32ac | |||
| 3393da2560 | |||
| 7566f94690 | |||
| ee8312fb57 | |||
| 76219704f3 | |||
| 2a43d4e88a | |||
| f0ce073a37 | |||
| 8047196394 | |||
| 3cf52b3985 | |||
| ac378e448b | |||
| daae2038f8 | |||
| a2356c6e59 | |||
| 44b91c6f07 | |||
| 144565d13e | |||
| 07fd66863d | |||
| 385a5f7c33 | |||
| 8c8c2edc66 | |||
| f7e393bded | |||
| 1df25e34b0 | |||
| ed491bad21 | |||
| 306f2d5738 | |||
| 2196cb95c0 | |||
| b4d494a5c7 | |||
| e8432f575e | |||
| eb48723c08 | |||
| 65168202b4 | |||
| dced3cf881 | |||
| 170fd6026c | |||
| 1ccaa817b3 | |||
| 65ba083720 | |||
| dba902d262 | |||
| d52c7a3afa | |||
| c63f88fb7f | |||
| 4412e0598f | |||
| eb22c1168e | |||
| 917eda356d | |||
| bc6580bbdc | |||
| 920cf622c5 | |||
| 13dcb193dc | |||
| 3afb724fed | |||
| 9662936b44 | |||
| 4780472d48 | |||
| 6f68c4504b | |||
| 28e0cdfe63 | |||
| a5003526c8 | |||
| ec3d78b509 | |||
| 4205e38605 | |||
| 8438d48c71 | |||
| 52faf5996b | |||
| 69eccc50a3 | |||
| 54cbdb236f | |||
| 34c374de1a | |||
| 113722e4ba | |||
| 3b429909f1 | |||
| f1cdf4552f | |||
| 7d320e57da | |||
| e1a598a459 | |||
| 6107842303 | |||
| 65372d2018 | |||
| 4842385ca6 | |||
| 05f55fc1ca | |||
| e8153d4b42 | |||
| 2087a659f9 | |||
| 1770336c11 | |||
| 9c177f6286 | |||
| 93a2c6d99f | |||
| 5ef76873b7 | |||
| b97c3fdb87 | |||
| 64838ad864 | |||
| 4df2ce5744 | |||
| c1f42836d9 | |||
| 0cbc9438d4 | |||
| ca6cd534d8 | |||
| f529e803ef | |||
| e5bc84badf | |||
| e2077bed59 | |||
| 9ab9d29efb | |||
| 5373a674e1 | |||
| bfaa2fafaf | |||
| d4a13a13d4 | |||
| 92c0368dd8 | |||
| 80c0426768 | |||
| 71b21289ab | |||
| fa861ff314 | |||
| 400d2dee32 | |||
| 3993dda17d | |||
| b1255efdcd | |||
| 22036c6a94 | |||
| f2df4b7d7d | |||
| 7c57bf78a1 | |||
| 8d898ff477 | |||
| a1045dbce9 | |||
| 23a07f3f7b | |||
| 458d87fef1 | |||
| 82df86d09e | |||
| 858cad2995 | |||
| c0483cf12e | |||
| 57a54cf016 | |||
| 325d8cde08 | |||
| 9e2c76c3bc | |||
| 996b161c1e | |||
| 831ef88431 | |||
| 89517e5988 | |||
| 48dbcf75a9 | |||
| a56a9bc9c5 | |||
| 918103fa29 | |||
| c9b8f5a739 | |||
| d58ff36d80 | |||
| 00d647c0ad | |||
| a27f196578 | |||
| abe82b79ce | |||
| 95230c3a23 | |||
| eade69a12c | |||
| 4f89e214ef | |||
| 2936679326 | |||
| 18a67fe1b5 | |||
| ba29c8ef6f | |||
| d253f1a020 | |||
| 03060d6ee2 | |||
| ac8ad3aae2 | |||
| 14e294efed | |||
| ec3205b372 | |||
| 45119d9a8c | |||
| 1065078f75 | |||
| 0ce904a985 | |||
| 254eec8e6a | |||
| 0e4cb4f6e1 | |||
| 9b05ac90df | |||
| f28a7fa508 | |||
| 65e95d8998 | |||
| 808b8f8e98 | |||
| 43b46a8be4 | |||
| 9bbfc71fc8 | |||
| 5d58b31f1c | |||
| e31a2238fb | |||
| 7de37bc9b7 | |||
| 13f7734751 | |||
| b4395bce99 | |||
| 7c82b08615 | |||
| 6498dbfbb7 | |||
| 74254aed4a | |||
| 8c48abf48e | |||
| 8958226604 | |||
| 005a6db230 | |||
| 4397abd8ab | |||
| e53882f555 | |||
| 5d5e61dfa1 | |||
| c5c6b70a89 | |||
| 6778a0e943 | |||
| 677e238c6d | |||
| b0bc2372fa | |||
| 6a808c7cc5 | |||
| d53543ccb1 | |||
| 88d7d54601 | |||
| 0f0c896887 | |||
| 65233c0a9a | |||
| 32e1e4788f | |||
| 181a78876b | |||
| 61d5446113 | |||
| c786569171 | |||
| d83a75e3f4 | |||
| a6f06559ea | |||
| 2084145dd3 | |||
| dd54058516 | |||
| 4056a54d44 | |||
| 191cd11741 | |||
| 800e74da67 | |||
| 237da47950 | |||
| ec12539fd0 | |||
| a537db559b | |||
| 2ba158df67 | |||
| c0c066547f | |||
| c3f59b14b9 | |||
| 1d06a8f1ad | |||
| efaf1295e9 | |||
| 4bc9cf3ad1 | |||
| 1e3851ea69 | |||
| b6d38536e3 | |||
| 9b4c678aa9 | |||
| a65775d85b | |||
| 192c7677c3 | |||
| 2a31a7b601 | |||
| 355fd7cb1d | |||
| 40efd73dfc | |||
| 31b144c76d | |||
| 31a46514cb | |||
| c8d906eb99 | |||
| 2895a1c819 | |||
| a4a5d08662 | |||
| b97c9d2165 | |||
| 5b7e8a69a5 | |||
| 9601e99e33 | |||
| d9a59f1b07 | |||
| b6a6af906a | |||
| 2c4799ce27 | |||
| b33b19849c | |||
| e398f36c57 | |||
| ee6064eee8 | |||
| 9d012c8f3c | |||
| 8888dbba0a | |||
| 6f8d5c1eb4 | |||
| 62da4a3d5c | |||
| 5cfc8e71a5 | |||
| a599760891 | |||
| 00324f668f | |||
| d4549704b9 | |||
| 0836e3d693 | |||
| 0058c3053d | |||
| 2a1a0a23a5 | |||
| 5614e3bd59 | |||
| acbda3cde1 | |||
| 0755436458 | |||
| 3daf405ae2 | |||
| 53dc3b0c03 | |||
| 371dcc5091 | |||
| 905fe54fa3 | |||
| 62c0827ff5 | |||
| d6c65ce81a | |||
| 5532b4a4d7 | |||
| 637bd3347b | |||
| bac373a818 | |||
| f0e7ea5640 | |||
| 4b981e0fad | |||
| 8fb9d861de | |||
| 199269ba3c | |||
| 0b9ec4c52c | |||
| 0e51565cc8 | |||
| 0418d35b67 | |||
| bf9d25363b | |||
| 84f00c57d1 | |||
| 7c1a5421fa | |||
| 3815bfe980 | |||
| de448c3192 | |||
| 967e2dfde6 | |||
| dd3c1eac5f | |||
| abc0a794c1 |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+17
-21
@@ -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"]
|
||||||
|
|||||||
@@ -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/).
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
+4
@@ -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;
|
||||||
+4
@@ -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);
|
||||||
+9
@@ -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,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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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]
|
||||||
|
|||||||
@@ -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 = ();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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]
|
||||||
|
|||||||
@@ -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::*;
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 |_| {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ init_i18n!(
|
|||||||
en,
|
en,
|
||||||
eo,
|
eo,
|
||||||
es,
|
es,
|
||||||
|
eu,
|
||||||
fa,
|
fa,
|
||||||
fi,
|
fi,
|
||||||
fr,
|
fr,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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(¬e.object_props.to)
|
let public_visibility = is_public(¬e.to())
|
||||||
|| is_public(¬e.object_props.bto)
|
|| is_public(¬e.bto())
|
||||||
|| is_public(¬e.object_props.cc)
|
|| is_public(¬e.cc())
|
||||||
|| is_public(¬e.object_props.bcc);
|
|| is_public(¬e.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(¬e.object_props.content_string()?),
|
content: SafeString::new(
|
||||||
spoiler_text: note.object_props.summary_string().unwrap_or_default(),
|
¬e
|
||||||
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,
|
||||||
¬e.object_props.attributed_to_link::<Id>()?,
|
¬e
|
||||||
|
.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(())
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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(())
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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, I’m 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, I’m 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
@@ -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
@@ -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
@@ -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
@@ -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 l’editor de text enriquit"
|
msgstr "Obre l’editor 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 d’iniciar una sessió"
|
msgstr "Per a crear un blog nou, heu d’iniciar 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 "S’ha creat el vostre blog correctament."
|
msgstr "S’ha 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 "S’ha suprimit el vostre blog."
|
msgstr "S’ha 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 "S’ha actualitzat la informació del vostre blog."
|
msgstr "S’ha actualitzat la informació del vostre blog."
|
||||||
|
|
||||||
@@ -109,6 +113,22 @@ msgstr "S’ha publicat el vostre comentari."
|
|||||||
msgid "Your comment has been deleted."
|
msgid "Your comment has been deleted."
|
||||||
msgstr "S’ha suprimit el vostre comentari."
|
msgstr "S’ha 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 "S’ha actualitzat el vostre article."
|
msgstr "S’ha 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 "S’ha desat el vostre article."
|
msgstr "S’ha 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 "S’ha suprimit el vostre article."
|
msgstr "S’ha 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 "S’ha reinicialitzat la vostra contrasenya correctament."
|
msgstr "S’ha 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 d’entrada."
|
||||||
|
|
||||||
|
msgid "We sent a mail to the address you gave us, with a link for registration."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Username"
|
||||||
|
msgstr "Nom d’usuari"
|
||||||
|
|
||||||
|
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 d’usuari"
|
|
||||||
|
|
||||||
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 d’entrada."
|
|
||||||
|
|
||||||
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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user