Compare commits

...

119 Commits

Author SHA1 Message Date
KitaitiMakoto 5885cb5b09 Merge pull request 'Release 0.6.0' (#855) from release0.6.0 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/855
2020-12-29 07:31:59 +00:00
Kitaiti Makoto f5ca464b16 Fix Matrix room ID in README 2020-12-29 16:29:43 +09:00
Kitaiti Makoto 1c40d5d3bc Add release date to CHANGELOG 2020-12-29 16:27:29 +09:00
Kitaiti Makoto 7e1abc527f Re-version to v0.6.0 2020-12-29 16:27:20 +09:00
KitaitiMakoto 39b2ef72fd Merge pull request 'Update CHANGELOG' (#854) from changelog into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/854
2020-12-28 13:19:41 +00:00
Kitaiti Makoto 17b081a21f Mark 0.6.0 unreleased 2020-12-28 22:18:46 +09:00
Kitaiti Makoto c482b1b552 Run cargo fmt 2020-12-28 22:15:45 +09:00
Kitaiti Makoto 8b16b9aaa4 Add changelog about support for RTL languages 2020-12-28 22:15:14 +09:00
KitaitiMakoto 11acc4172c Merge pull request 'add support for right to left languages in post content' (#853) from bidi-md into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/853
Reviewed-by: KitaitiMakoto <kitaitimakoto@noreply@joinplu.me>
2020-12-28 12:22:24 +00:00
Trinity Pointard 6187c62cb3 add bidi via forked pulldown-cmark 2020-12-27 21:02:37 +01:00
Trinity Pointard d6b5b9a721 upgrade to pulldown v0.8 2020-12-27 20:38:23 +01:00
KitaitiMakoto 80675f85f3 Merge pull request 'resynth1943/patch-1' (#852) from resynth1943/patch-1 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/852
2020-12-27 16:44:10 +00:00
Resynth 07a6be424f Update alias 2020-12-26 12:48:39 +00:00
KitaitiMakoto 8cc56d8d81 Merge pull request 'Validate proofing for all activities' (#851) from prevent-spoof into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/851
2020-12-24 06:23:49 +00:00
Resynth f8cabcc904 Change riot.im link to matrix.to 2020-12-23 20:59:22 +00:00
Kitaiti Makoto d6a946f5b9 Validate spoofing for all activities 2020-12-22 16:31:12 +09:00
Kitaiti Makoto 2a5a2ec4d2 (cargo-release) start next development iteration {{next_version}} 2020-12-19 11:24:59 +00:00
Kitaiti Makoto 106e9417d3 (cargo-release) version {{version}} 2020-12-19 11:24:59 +00:00
Kitaiti Makoto 2660efec16 Define tag-name for cargo-release 2020-12-19 11:24:59 +00:00
Kitaiti Makoto 1eaa5a6e31 Add pre-release-hook to release.toml of subcrates to prevent running
one of root crate
2020-12-19 11:24:59 +00:00
Kitaiti Makoto b2c26f03e8 Pull master branch from Crowdin 2020-12-19 11:24:59 +00:00
Kitaiti Makoto a75cdaa870 Move security fix to next release in CHANGELOG 2020-12-19 11:24:59 +00:00
Kitaiti Makoto 2047355d46 Validate attributedTo in the case it is an object 2020-12-19 11:24:59 +00:00
Kitaiti Makoto 17bbcd1a4b Add changelog about security fix 2020-12-19 11:24:59 +00:00
Kitaiti Makoto 5c8170a97d Validate spoofing of Create activity 2020-12-19 11:24:59 +00:00
Kitaiti Makoto beaeaf743a Add test for preventing spoofing 2020-12-19 11:24:59 +00:00
Kitaiti Makoto fe641620a6 Update translations 2020-12-19 11:24:59 +00:00
Kitaiti Makoto 3e1a6e61ad Add *.pot 2020-12-19 11:24:59 +00:00
Kitaiti Makoto 45b46838ae Merge branch 'main' of git.joinplu.me:Plume/Plume into main 2020-12-19 07:26:11 +09:00
Kitaiti Makoto 46cdde8687 (cargo-release) start next development iteration {{next_version}} 2020-12-19 07:22:24 +09:00
Kitaiti Makoto 6b0dfb729c (cargo-release) version {{version}} 2020-12-19 07:22:19 +09:00
Kitaiti Makoto 75a4d1abf1 Define tag-name for cargo-release 2020-12-19 07:03:40 +09:00
Kitaiti Makoto 984c0cda6b Add pre-release-hook to release.toml of subcrates to prevent running
one of root crate
2020-12-19 06:57:09 +09:00
Kitaiti Makoto 902612a470 Pull master branch from Crowdin 2020-12-19 06:21:59 +09:00
Kitaiti Makoto e311d4d423 Move security fix to next release in CHANGELOG 2020-12-19 06:04:23 +09:00
Kitaiti Makoto aa33c8e7d3 Merge branch 'validate-attributed-to' into main 2020-12-19 06:01:45 +09:00
Kitaiti Makoto 60e6d49319 Update translations 2020-12-19 05:59:58 +09:00
Kitaiti Makoto 6dbd9b76fb Add *.pot 2020-12-19 05:56:36 +09:00
KitaitiMakoto e3877d3022 Fill changelogs for the next release (#842)
Clarify purpose of adding Vazir font

Fill changelogs for the next release

Co-authored-by: Kitaiti Makoto <KitaitiMakoto@gmail.com>
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/842
2020-12-18 14:30:51 +00:00
KitaitiMakoto 4c11df65dc Merge pull request 'Pull request template' (#848) from pr-template into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/848
2020-12-13 10:03:21 +00:00
Kitaiti Makoto f8cfb8cacf Move GitHub pull request template 2020-12-13 19:01:55 +09:00
Kitaiti Makoto 9a7e10b560 Merge branch 'main' of git.joinplu.me:Plume/Plume into main 2020-12-13 19:01:16 +09:00
KitaitiMakoto e131330175 Add guide to GitHub pull request (#847)
Add guide to pull request

Co-authored-by: Kitaiti Makoto <KitaitiMakoto@gmail.com>
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/847
2020-12-13 09:57:48 +00:00
KITAITI Makoto b9066cb7d0 Update issue templates
Guide to use Gitea issues.
2020-12-13 18:50:50 +09:00
KitaitiMakoto 4c4d9fafd5 Merge pull request 'Fix CI environment' (#843) from fix-ci-env into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/843
2020-12-10 16:31:16 +00:00
Kitaiti Makoto bbdf2a8293 Merge branch 'main' into validate-attributed-to 2020-12-11 01:14:22 +09:00
KitaitiMakoto 0d3e1a233f Merge pull request 'Update Cargo.lock' (#844) from cargo-lock into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/844
2020-12-10 16:13:37 +00:00
Kitaiti Makoto 712bc2afbe Update Cargo.lock 2020-12-11 01:12:15 +09:00
Kitaiti Makoto 2eadb80435 Validate attributedTo in the case it is an object 2020-12-11 00:35:20 +09:00
Kitaiti Makoto de05b9e176 Add changelog about security fix 2020-12-09 23:45:56 +09:00
Kitaiti Makoto 5bf1e81f0b Merge branch 'main' into validate-attributed-to 2020-12-09 23:43:29 +09:00
Kitaiti Makoto 55abbff9b8 Use Caddy v2 on build env 2020-12-08 21:51:09 +09:00
Kitaiti Makoto 5f89401a7c Update Rust on buildenv image 2020-12-08 20:06:21 +09:00
KitaitiMakoto 7bd3c1b01e Merge pull request 'Fix #763 Update Dockerfile.dev' (#841) from update-dockerfile-dev into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/841
2020-12-07 02:06:00 +00:00
KitaitiMakoto 961806247c Merge branch 'main' into update-dockerfile-dev 2020-12-07 02:05:34 +00:00
Kitaiti Makoto ac7ffae121 Update plumeorg/plume-buildenv used on CI 2020-12-07 07:50:33 +09:00
Kitaiti Makoto 5321f3cf18 Add clang dependency in Dockerfile.dev 2020-12-07 07:47:11 +09:00
KitaitiMakoto 5099a00c6b Merge pull request 'better-caching' (#840) from better-caching into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/840
2020-12-06 17:32:33 +00:00
KitaitiMakoto d44c034f6a Merge branch 'main' into better-caching 2020-12-06 17:31:53 +00:00
KitaitiMakoto 963e742dcf Merge pull request 'Run cargo fmt' (#839) from fmt into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/839
2020-12-06 17:05:39 +00:00
Kitaiti Makoto 50462204e8 Run cargo fmt 2020-12-07 02:04:37 +09:00
KitaitiMakoto 174ed738bd Merge pull request 'Update Docker image for testing' (#838) from update-build-image into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/838
2020-12-06 17:02:22 +00:00
Kitaiti Makoto f8d098f353 Update Docker image for testing 2020-12-07 01:58:41 +09:00
KitaitiMakoto f5b18dffaf cargo release helper (again) (#835)
remove tag-prefix deletion

as discussed here: https://github.com/igalic/Plume/pull/1

Update all versions numbers

Update Crowdin config to v3 + Pull translations with cargo release

Follow a more standard CHANGELOG format

add release.toml configuration file for cargo-release

and also add overrides in all other crates, since we only have one
CHANGELOG.md: https://github.com/sunng87/cargo-release/issues/205
we might wanna have that looked at or fixed

ignore .vscode/

add / backfill Changelog.md

Co-authored-by: Mina Galić <me+git@igalic.co>
Co-authored-by: Ana Gelez <ana@gelez.xyz>
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/835
2020-12-06 15:52:27 +00:00
KitaitiMakoto 002d1f1720 Merge pull request 'Update distro of Docker image for testing' (#834) from test-buster into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/834
2020-12-06 13:28:54 +00:00
Kitaiti Makoto 23a5634035 Update distro of Docker image for testing 2020-12-06 22:26:30 +09:00
Kitaiti Makoto 5cd8ae9106 Validate spoofing of Create activity 2020-12-03 19:20:10 +09:00
Kitaiti Makoto 350697f89a Run cargo fmt 2020-12-03 19:20:10 +09:00
Kitaiti Makoto 569979374f Add test for preventing spoofing 2020-12-03 19:20:10 +09:00
KitaitiMakoto 3c666080a9 Merge pull request 'Keep tags as-is' (#832) from KitaitiMakoto/Plume:keep-tag into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/832
Reviewed-by: kiwii <kiwii@noreply@joinplu.me>
2020-11-22 16:21:42 +00:00
Kitaiti Makoto 4e6d7b70cf Keep tags as-is 2020-11-22 22:24:43 +09:00
kiwii a59f167771 Merge pull request 'PR #818 from Github' (#831) from gh-pr-818 into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/831
2020-11-22 11:45:47 +00:00
Ana Gelez f28ab19dc0 Merge branch 'serra-allgood/#754-display-error-for-duplicated-blocklist-email' of https://github.com/serra-allgood/Plume into gh-pr-818 2020-11-22 12:43:46 +01:00
KitaitiMakoto 6dd4080d65 Merge pull request 'Remove link to unimplemented page(timeline edit)' (#827) from KitaitiMakoto/Plume:feed-edit-button into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/827
Reviewed-by: Mina Galić <me+git@igalic.co>
2020-11-15 17:10:55 +00:00
Kitaiti Makoto eac76e6806 Merge remote-tracking branch 'upstream/main' into feed-edit-button 2020-11-12 00:05:42 +09:00
trinity-1686a 0cd26dfbf4 fix test following ldap udpate
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/828
Reviewed-by: Mina Galić <me+git@igalic.co>
2020-10-08 19:18:04 +00:00
Trinity Pointard 3de009713d try fixing clippy and fmt 2020-10-08 20:24:03 +02:00
Trinity Pointard 69bcb01715 fix test following ldap udpate 2020-10-08 19:59:54 +02:00
trinity-1686a 9ec2d93f50 implement login via LDAP
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/826
Reviewed-by: Mina Galić <me+git@igalic.co>
2020-10-08 16:11:35 +00:00
Trinity Pointard 4da9b24cb1 cargo fmt 2020-10-07 23:40:27 +02:00
Trinity Pointard f40634aa97 address comments 2020-10-07 23:39:38 +02:00
Kitaiti Makoto 83282e0736 Remove link to unimplemented page 2020-10-06 19:47:35 +09:00
Serra Allgood 43fde90387 Fix displaying not found page when submitting a duplicated blocklist email
Signed-off-by: Serra Allgood <serra@allgood.dev>
2020-10-05 20:17:30 -07:00
Trinity Pointard ccba8163c9 fix compilation issue with cli 2020-10-04 23:53:14 +02:00
Trinity Pointard 8975b0f9e9 cargo fmt 2020-10-04 12:18:54 +02:00
Trinity Pointard d626f3366d add support for ldap 2020-10-04 12:18:22 +02:00
Trinity Pointard b24f195e10 refactor login
first step toward ldap
should have no functionnal change
2020-10-03 13:21:31 +02:00
Mina Galić 003dcf861a Merge pull request 'CSS Fix: My title is displayed at the wrong position on Webkit browsers' (#825) from quentin/Plume:pr/flex into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/825
Reviewed-by: Mina Galić <me+git@igalic.co>
2020-09-29 07:15:06 +00:00
Quentin Dufour 5d8efa77a3 Fix title 2020-09-25 11:19:24 +02:00
kiwii fb6d49cc6e Merge pull request 'GPU acceleration for the mobile menu' (#818) from gpu-acceleration into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/818
Reviewed-by: kiwii <kiwii@noreply@joinplu.me>
2020-08-27 10:29:34 +00:00
Mina Galić 48776939f4 Merge branch 'main' into gpu-acceleration 2020-08-19 19:53:39 +00:00
Marek Ľach 104b6c1d0c GPU acceleration for the mobile menu
Reimplements #810, but for all browsers.
2020-08-19 21:34:07 +02:00
Mina Galić 3881927550 Merge pull request 'mobile-margins' (#817) from mobile-margins into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/817
Reviewed-by: Mina Galić <me+git@igalic.co>
2020-08-19 11:35:04 +00:00
Ana Gelez 0bec13edc0 Improve responsiveness 2020-08-11 20:13:27 +02:00
Ana Gelez 191d6da486 Move header-related style to _header.scss
It seems to fix the overflowing menu items on small screens too
2020-08-11 20:13:27 +02:00
kiwii 9aaff1a484 Merge pull request 'Use result of local storage insert operation to suppress Clippy' (#810) from KitaitiMakoto/Plume:suppress-clippy into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/810
Reviewed-by: kiwii <kiwii@noreply@joinplu.me>
2020-07-25 17:28:26 +00:00
Kitaiti Makoto 36fd55a7e4 Use result of local storage insert operation to suppress Clippy 2020-07-25 20:13:41 +09:00
kiwii fc474bf8d1 Merge pull request '[BUG FIX]Make it possible to switch to rich text editor' (#808) from KitaitiMakoto/Plume:richtexteditor into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/808
Reviewed-by: kiwii <kiwii@noreply@joinplu.me>
2020-07-23 18:25:53 +00:00
Kitaiti Makoto a050deb557 [BUG FIX]Make it possible to switch to rich text editor 2020-07-23 23:36:09 +09:00
kiwii 6de9a1f1c8 Merge pull request 'Recreate search index if its format is outdated' (#802) from KitaitiMakoto/Plume:invalid-index into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/802
Reviewed-by: Mina Galić <me+git@igalic.co>
2020-07-19 12:04:43 +00:00
Kitaiti Makoto 5e30bede40 Don't care about needless return value of closures 2020-07-19 08:24:17 +09:00
Kitaiti Makoto 5cca66b346 Add action user can take to error message 2020-07-19 07:16:34 +09:00
Kitaiti Makoto 484659fde2 Run cargo fmt 2020-07-18 22:52:39 +09:00
Kitaiti Makoto 5fc827c1c9 Re-initialize search index when recreating is failed 2020-07-18 21:00:58 +09:00
Kitaiti Makoto 50753b3a65 Recreate search index if its format is outdated 2020-07-18 09:40:58 +09:00
Kitaiti Makoto c5d03d300b Cause IndexInvalidDataError when search index is invalid 2020-07-18 09:40:47 +09:00
kiwii 010eac6c4a Merge pull request 'upgrade rocket* to the latest stable' (#800) from igalic/Plume:update/rocket into main
Reviewed-on: https://git.joinplu.me/Plume/Plume/pulls/800
Reviewed-by: kiwii <kiwii@noreply@joinplu.me>
2020-07-17 13:39:12 +00:00
Mina Galić a107d35492 upgrade rocket* to the latest stable
before we embark on upgrading to async, and all the refactoring that
this will bring on us (see #797 & #799), we should keep our `main`
branch as stable and current as possible.
Let's start by upgrading rocket and its dependencies.
2020-07-17 09:38:55 +02:00
Kitaiti Makoto f9beb2383b Update lindera-tantivy to v0.1.3 2020-07-05 17:59:51 +02:00
Chosto 7e78cffcaa Bump Docker base images to buster flavor (#797) 2020-07-02 12:46:56 +02:00
Ana Gelez 719fec0828 Add a comment 2020-06-25 22:26:44 +02:00
Ana Gelez 90fdade566 Better caching
Based on @trinity-1686a's patch: https://github.com/Plume-org/Plume/issues/786#issuecomment-647170325
2020-06-25 22:20:31 +02:00
TheMasix 28576c1fa3 Add Vazir Font (#787)
* Add Vazir Font

* Add Vazir Font License

* fix prettier problem

* Shabnam added
2020-06-24 15:48:04 +02:00
Gelez d99b42582d Add Persian to the list of languages (#782) 2020-06-17 18:33:58 +02:00
KITAITI Makoto 92a386277b Switchable tokenizer (#776)
* [REFACTORING]Rename whitespace_tokenizer to tag_tokenizer for
registration

Name representing its purpose is preferred.

* Add lindera-tantivy to plume-model's dependencies

* Install lindera-tantivy

* Add SearchTokenizerConfig struct

* Add search tokenizers to config option

* Use CONFIG for tokenizers

* Use enum to hold tokenizer config instead of initializing on config phase

* Use guard instead of duplicate default values

* Use as_deref() instead of guard

* Move SearchTokenizer from plume-models to plume-models::search::tokenizer

* Rename SearchTokenizer to TokenizerKind

* Define SearchTokenierConfig::determine_tokenizer()

* Use determine_tokenizer in SearchTokenizerConfig::init()

* Pass tokenizer config to Searcher methods

* Add LowerCase filter to Lindera tokenizer

* Add test for Lindera tokenizer

* Define SEARCH_LANG env to specify tokenizers set

* Run cargo fmt

* Make Lindera tokenizer optional

* Fix typos
2020-06-17 16:57:28 +02:00
Gelez 297d9fcf40 Don't show boosts and likes for "all" and "local" in timelines (#781)
Fixes #711
2020-06-15 19:50:28 +02:00
KITAITI Makoto ef70cb93e6 Upgrade Tantivy to v0.12.0 (#771)
* Upgrade Tantivy to 0.12.0

* Follow Tantivy Tokenizer's new type definition

* Wrap tokenizers with TextAnalyzer to use filter methods

* Replace async IndexWriter::garbage_collect_files with sync functions

* Update Cargo.toml
2020-05-20 13:31:45 +02:00
Daniel Watkins efb76a3c17 remove dependency on runtime-fmt (#773)
Per the issue, "runtime-fmt uses perma-unstable rust APIs and is
therefore susceptible to breakage".

This replaces the two calls to rt_format! with .replace() and drops the
dependency.

Fixes #769
2020-05-18 20:18:07 +02:00
KITAITI Makoto 197f0d7ecd Add test for hash including ZWSP (#772)
* Add test for hash including ZWSP

* Run cargo fmt
2020-05-17 13:53:31 +02:00
191 changed files with 28070 additions and 20873 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ executors:
type: boolean type: boolean
default: false default: false
docker: docker:
- image: plumeorg/plume-buildenv:v0.0.9 - image: plumeorg/plume-buildenv:v0.3.0
- image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>> - image: <<#parameters.postgres>>circleci/postgres:9.6-alpine<</parameters.postgres>><<^parameters.postgres>>alpine:latest<</parameters.postgres>>
environment: environment:
POSTGRES_USER: postgres POSTGRES_USER: postgres
+2 -5
View File
@@ -1,6 +1,3 @@
localhost:443 { localhost {
proxy / localhost:7878 { reverse_proxy localhost:7878
transparent
}
tls self_signed
} }
+7 -5
View File
@@ -1,14 +1,17 @@
FROM debian:stretch-20190326 FROM debian:buster-20201117
ENV PATH="/root/.cargo/bin:${PATH}" ENV PATH="/root/.cargo/bin:${PATH}"
#install native/circleci/build dependancies #install native/circleci/build dependancies
RUN apt update &&\ RUN apt update &&\
apt install -y --no-install-recommends git ssh tar gzip ca-certificates default-jre&&\ apt install -y --no-install-recommends git ssh tar gzip ca-certificates default-jre&&\
apt install -y --no-install-recommends binutils-dev build-essential cmake curl gcc gettext git libcurl4-openssl-dev libdw-dev libelf-dev libiberty-dev libpq-dev libsqlite3-dev libssl-dev make openssl pkg-config postgresql postgresql-contrib python zlib1g-dev python3-pip zip unzip libclang-dev&&\ echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \
| tee -a /etc/apt/sources.list.d/caddy-fury.list &&\
apt update &&\
apt install -y --no-install-recommends binutils-dev build-essential cmake curl gcc gettext git libcurl4-openssl-dev libdw-dev libelf-dev libiberty-dev libpq-dev libsqlite3-dev libssl-dev make openssl pkg-config postgresql postgresql-contrib python zlib1g-dev python3-pip zip unzip libclang-dev clang caddy&&\
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
#install and configure rust #install and configure rust
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2020-01-15 -y &&\ RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2020-12-07 -y &&\
rustup component add rustfmt clippy &&\ rustup component add rustfmt clippy &&\
rustup component add rust-std --target wasm32-unknown-unknown rustup component add rust-std --target wasm32-unknown-unknown
@@ -24,8 +27,7 @@ COPY cargo_config /root/.cargo/config
#install selenium for front end tests #install selenium for front end tests
RUN pip3 install selenium RUN pip3 install selenium
#install and configure caddy #configure caddy
RUN curl https://getcaddy.com | bash -s personal
COPY Caddyfile /Caddyfile COPY Caddyfile /Caddyfile
#install crowdin #install crowdin
+9
View File
@@ -45,3 +45,12 @@ ROCKET_ADDRESS=127.0.0.1
#PLUME_LOGO_192=icons/trwnh/paragraphs/plumeParagraphs192.png #PLUME_LOGO_192=icons/trwnh/paragraphs/plumeParagraphs192.png
#PLUME_LOGO_256=icons/trwnh/paragraphs/plumeParagraphs256.png #PLUME_LOGO_256=icons/trwnh/paragraphs/plumeParagraphs256.png
#PLUME_LOGO_512=icons/trwnh/paragraphs/plumeParagraphs512.png #PLUME_LOGO_512=icons/trwnh/paragraphs/plumeParagraphs512.png
## LDAP CONFIG ##
# the object that will be bound is "${USER_NAME_ATTR}=${username},${BASE_DN}"
#LDAP_ADDR=ldap://127.0.0.1:1389
#LDAP_BASE_DN="ou=users,dc=your-org,dc=eu"
#LDAP_USER_NAME_ATTR=cn
#LDAP_USER_MAIL_ATTR=mail
#LDAP_TLS=false
+10
View File
@@ -7,6 +7,16 @@ assignees: ''
--- ---
<!--
We would appreciated if you report a bug at our Gitea instance's issue page:
https://git.joinplu.me/Plume/Plume/issues
You can login to the Gitea with your GitHub account.
We welcome to receive bug reports here, GitHub, too.
-->
<!-- Describe your bug, explaining how to reproduce it, and what was expected --> <!-- Describe your bug, explaining how to reproduce it, and what was expected -->
@@ -7,6 +7,15 @@ assignees: ''
--- ---
<!--
We would appreciated if you request a feature at our Gitea instance's issue page:
https://git.joinplu.me/Plume/Plume/issues
You can login to the Gitea with your GitHub account.
We welcome to receive feature requests here, GitHub, too.
-->
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+7
View File
@@ -0,0 +1,7 @@
<!--
We would appreciated if you report a bug at our Gitea instance's pull request page:
https://git.joinplu.me/Plume/Plume/pulls
You can login to the Gitea with your GitHub account.
We welcome to receive pull requests here, GitHub, too.
-->
+1
View File
@@ -18,3 +18,4 @@ tags.*
search_index search_index
.buildconfig .buildconfig
__pycache__ __pycache__
.vscode/
+191
View File
@@ -0,0 +1,191 @@
# Changelog
<!-- next-header -->
## [Unreleased] - ReleaseDate
## [[0.6.0]] - 2020-12-29
### Added
- Vazir font for better support of languages written in Arabic script (#787)
- Login via LDAP (#826)
- cargo-release (#835)
- Care about weak ETag header for better caching (#840)
- Support for right to left languages in post content (#853)
### Changed
- Bump Docker base images to Buster flavor (#797)
- Upgrade Rocket to 0.4.5 (#800)
- Keep tags as-is (#832)
- Update Docker image for testing (#838)
- Update Dockerfile.dev (#841)
### Fixed
- Recreate search index if its format is outdated (#802)
- Make it possible to switch to rich text editor (#808)
- Fix margins for the mobile devices (#817)
- GPU acceleration for the mobile menu (#818)
- Natural title position for RtoL languages (#825)
- Remove link to unimplemented page (#827)
- Fix displaying not found page when submitting a duplicated blocklist email (#831)
### Security
- Validate spoofing of Create activity
## [0.5.0] - 2020-06-21
### Added
- Email blocklisting (#718)
- Syntax highlighting (#691)
- Persian localization (#782)
- Switchable tokenizer - enables Japanese full-text search (#776)
- Make database connections configurable by environment variables (#768)
### Changed
- Display likes and boost on post cards (#744)
- Rust 2018 (#726)
- Bump to LLVM to 9.0.0 to fix ARM builds (#737)
- Remove dependency on runtime-fmt (#773)
- Drop the -alpha suffix in release names, it is implied that Plume is not stable yet because of the 0 major version (Plume 1.0.0 will be the first stable release).
### Fixed
- Fix parsing of mentions inside a Markdown code block (be430c6)
- Fix RSS issues (#720)
- Fix Atom feed (#764)
- Fix default theme (#746)
- Fix shown password on remote interact pages (#741)
- Allow unicode hashtags (#757)
- Fix French grammar for for 0 (#760)
- Don't show boosts and likes for "all" and "local" in timelines (#781)
- Fix liking and boosting posts on remote instances (#762)
## [0.4.0] - 2019-12-23
### Added
- Add support for generic timeline (#525)
- Federate user deletion (#551)
- import migrations and don't require diesel_cli for admins (#555)
- Cache local instance (#572)
- Initial RTL support #575 (#577)
- Confirm deletion of blog (#602)
- Make a distinction between moderators and admins (#619)
- Theming (#624)
- Add clap to plume in order to print help and version (#631)
- Add Snapcraft metadata and install/maintenance hooks (#666)
- Add environmental variable to control path of media (#683)
- Add autosaving to the editor (#688)
- CI: Upload artifacts to pull request deploy environment (#539)
- CI: Upload artifact of wasm binary (#571)
### Changed
- Update follow_remote.rs.html grammar (#548)
- Add some feedback when performing some actions (#552)
- Theme update (#553)
- Remove the new index lock tantivy uses (#556)
- Reduce reqwest timeout to 5s (#557)
- Improve notification management (#561)
- Fix occurrences of 'have been' to 'has been' (#578) + Direct follow-up to #578 (#603)
- Store password reset requests in database (#610)
- Use futures and tokio to send activities (#620)
- Don't ignore dotenv errors (#630)
- Replace the input! macro with an Input builder (#646)
- Update default license (#659)
- Paginate the outbox responses. Fixes #669 (#681)
- Use the "classic" editor by default (#697)
- Fix issue #705 (#708)
- Make comments in styleshhets a bit clearer (#545)
- Rewrite circleci config (#558)
- Use openssl instead of sha256sum for build.rs (#568)
- Update dependencies (#574)
- Refactor code to use Shrinkwraprs and diesel-derive-newtype (#598)
- Add enum containing all successful route returns (#614)
- Update dependencies which depended on nix -- fixes arm32 builds (#615)
- Update some documents (#616)
- Update dependencies (#643)
- Make the comment syntax consistent across all CSS (#487)
### Fixed
- Remove r (#535)
- Fix certain improper rendering of forms (#560)
- make hashtags work in profile summary (#562)
- Fix some federation issue (#573)
- Prevent comment form submit button distortion on iOS (#592)
- Update textarea overflow to scroll (#609)
- Fix arm builds (#612)
- Fix theme caching (#647)
- Fix issue #642, frontend not in English if the user language does not exist (#648)
- Don't index drafts (#656)
- Fill entirely user on creation (#657)
- Delete notification on user deletion (#658)
- Order media so that latest added are top (#660)
- Fix logo URL (#664)
- Snap: Ensure cargo-web doesn't erroneously adopt our workspace. (#667)
- Snap: Another fix for building (#668)
- Snap: Fix build for non-Tier-1 Rust platforms (#672)
- Don't split sentences for translations (#677)
- Escape href quotation marks (#678)
- Re-add empty strings in translation (#682)
- Make the search index creation during migration respect SEARCH_INDEX (#689)
- Fix the navigation menu not opening on touch (#690)
- Make search items optional (#693)
- Various snap fixes (#698)
- Fix #637 : Markdown footnotes (#700)
- Fix lettre (#706)
- CI: Fix Crowdin upload (#576)
### Removed
- Remove the Canapi dependency (#540)
- Remove use of Rust in migrations (#704)
## [0.3.0] - 2019-04-19
### Added
- Cover for articles (#299, #387)
- Password reset (#448)
- New editor (#293, #458, #482, #483, #486, #530)
- Search (#324, #375, #445)
- Edit blogs (#460, #494, #497)
- Hashtags in articles (#283, #295)
- API endpoints (#245, #285, #307)
- A bunch of new translations! (#479, #501, #506, #510, #512, #514)
### Changed
- Federation improvements (#216, #217, #357, #364, #399, #443, #446, #455, #502, #519)
- Improved build process (#281, #374, #392, #402, #489, #498, #503, #511, #513, #515, #528)
### Fixes
- UI usability fixes (#370, #386, #401, #417, #418, #444, #452, #480, #516, #518, #522, #532)
## [0.2.0] - 2018-09-12
### Added
- Article publishing, or save as a draft
- Like, or boost an article
- Basic Markdown editor
- Federated commenting system
- User account creation
- Limited federation on other platforms and subscribing to users
- Ability to create multiple blogs
<!-- next-url -->
[Unreleased]: https://github.com/Plume-org/Plume/compare/0.6.0...HEAD
[[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.4.0]: https://github.com/Plume-org/Plume/compare/0.3.0-alpha-2...0.4.0-alpha-4
[0.3.0]: https://github.com/Plume-org/Plume/compare/0.2.0-alpha-1...0.3.0-alpha-2
[0.2.0]: https://github.com/Plume-org/Plume/releases/tag/0.2.0-alpha-1
Generated
+1599 -871
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,7 +1,7 @@
[package] [package]
authors = ["Plume contributors"] authors = ["Plume contributors"]
name = "plume" name = "plume"
version = "0.4.0" version = "0.6.0"
repository = "https://github.com/Plume-org/Plume" repository = "https://github.com/Plume-org/Plume"
edition = "2018" edition = "2018"
@@ -20,11 +20,10 @@ heck = "0.3.0"
lettre = "0.9.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.2" rocket = "0.4.5"
rocket_contrib = { version = "0.4.2", features = ["json"] } rocket_contrib = { version = "0.4.5", features = ["json"] }
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" } rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
rpassword = "4.0" rpassword = "4.0"
runtime-fmt = "0.4.0"
scheduled-thread-pool = "0.2.2" scheduled-thread-pool = "0.2.2"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
@@ -79,6 +78,7 @@ postgres = ["plume-models/postgres", "diesel/postgres"]
sqlite = ["plume-models/sqlite", "diesel/sqlite"] sqlite = ["plume-models/sqlite", "diesel/sqlite"]
debug-mailer = [] debug-mailer = []
test = [] test = []
search-lindera = ["plume-models/search-lindera"]
[workspace] [workspace]
members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"] members = ["plume-api", "plume-cli", "plume-models", "plume-common", "plume-front", "plume-macro"]
+2 -2
View File
@@ -1,4 +1,4 @@
FROM rust:1-stretch as builder FROM rust:1-buster as builder
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
@@ -28,7 +28,7 @@ RUN cargo install --path ./ --force --no-default-features --features postgres
RUN cargo install --path plume-cli --force --no-default-features --features postgres RUN cargo install --path plume-cli --force --no-default-features --features postgres
RUN cargo clean RUN cargo clean
FROM debian:stretch-slim FROM debian:buster-slim
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
+3 -2
View File
@@ -1,4 +1,4 @@
FROM rust:1-stretch FROM rust:1-buster
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
@@ -10,7 +10,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \ gcc \
make \ make \
openssl \ openssl \
libssl-dev libssl-dev\
clang
WORKDIR /scratch WORKDIR /scratch
COPY script/wasm-deps.sh . COPY script/wasm-deps.sh .
+1 -1
View File
@@ -34,7 +34,7 @@ rules and thematics, but they can all communicate with each other.
## Get involved ## Get involved
If you want to have regular news about the project, the best place is probably [our blog](https://fediverse.blog/~/PlumeDev), or our Matrix room: [`#plume:disroot.org`](https://riot.im/app/#/room/#plume:disroot.org). If you want to have regular news about the project, the best place is probably [our blog](https://fediverse.blog/~/PlumeDev), or our Matrix room: [`#plume-blog:matrix.org`](https://matrix.to/#/#plume-blog:matrix.org).
If you want to contribute more, a good first step is to read [our contribution guides](https://docs.joinplu.me/contribute). We accept all kind of contribution: If you want to contribute more, a good first step is to read [our contribution guides](https://docs.joinplu.me/contribute). We accept all kind of contribution:
+28 -1
View File
@@ -40,7 +40,7 @@ main header.article {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: end; justify-content: flex-end;
h1, .article-info { h1, .article-info {
text-align: center; text-align: center;
@@ -490,3 +490,30 @@ input:checked ~ .cw-container > .cw-text {
display: inline; display: inline;
} }
} }
// Small screens
@media screen and (max-width: 600px) {
#plume-editor header {
flex-direction: column-reverse;
button {
flex: 0 0 0;
}
}
.popup {
top: 10vh;
bottom: 10vh;
left: 1vw;
right: 1vw;
}
main article {
margin: 2.5em .5em;
max-width: none;
}
main .article-meta > *, main .article-meta .comments, main .article-meta > .banner > * {
margin: 0 5%;
}
}
+5 -1
View File
@@ -490,6 +490,10 @@ figure {
/// Small screens /// Small screens
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
body > main > *, .h-feed > * {
margin: 1em;
}
main .article-meta { main .article-meta {
> *, .comments { > *, .comments {
margin: 0 5%; margin: 0 5%;
@@ -535,7 +539,7 @@ figure {
margin: 0; margin: 0;
& > * { & > * {
max-width: 100%; max-width: 100% !important;
} }
} }
+91
View File
@@ -205,6 +205,7 @@ body > header {
position: absolute; position: absolute;
left: 50%; left: 50%;
transform: translate(-50%, 0); transform: translate(-50%, 0);
transform: translateZ(0);
opacity: 0; opacity: 0;
font-size: 0.9em; font-size: 0.9em;
white-space: nowrap; white-space: nowrap;
@@ -221,3 +222,93 @@ body > header {
} }
} }
} }
// Small screens
@media screen and (max-width: 600px) {
@keyframes menuOpening {
from {
transform: scaleX(0);
transform-origin: left;
opacity: 0;
}
to {
transform: scaleX(1);
transform-origin: left;
opacity: 1;
}
}
body > header {
flex-direction: column;
nav#menu {
display: inline-flex;
z-index: 21;
}
#content {
display: none;
appearance: none;
text-align: center;
z-index: 20;
}
}
body > header:focus-within #content, #content.show {
position: fixed;
display: flex;
flex-direction: column;
justify-content: flex-start;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
animation: 0.2s menuOpening;
&::before {
content: "";
position: absolute;
transform: skewX(-10deg);
top: 0;
left: -20%;
width: 100%;
height: 100%;
z-index: -10;
background: $primary;
}
> nav {
flex-direction: column;
align-items: flex-start;
a {
display: flex;
flex-direction: row;
align-items: center;
margin: 0;
padding: 1rem 1.5rem;
color: $background;
font-size: 1.4em;
font-weight: 300;
&.title { font-size: 1.8em; }
> *:first-child { width: 3rem; }
> img:first-child { height: 3rem; }
> *:last-child { margin-left: 1rem; }
> nav hr {
display: block;
margin: 0;
width: 100%;
border: solid $background 0.1rem;
}
.mobile-label { display: initial; }
}
}
}
}
+9 -9
View File
@@ -1,10 +1,10 @@
/* Color Scheme */ /* Color Scheme */
$gray: #F3F3F3; $gray: #f3f3f3;
$black: #242424; $black: #242424;
$white: #F8F8F8; $white: #f8f8f8;
$purple: #7765E3; $purple: #7765e3;
$lightpurple: #c2bbee; $lightpurple: #c2bbee;
$red: #E92F2F; $red: #e92f2f;
$yellow: #ffe347; $yellow: #ffe347;
$green: #23f0c7; $green: #23f0c7;
@@ -24,14 +24,14 @@ $margin: 0 $horizontal-margin;
/* Fonts */ /* Fonts */
$route159: "Route159", serif; $route159: "Shabnam", "Route159", serif;
$playfair: "Playfair Display", serif; $playfair: "Vazir", "Playfair Display", serif;
$lora: "Lora", serif; $lora: "Vazir", "Lora", serif;
//Code Highlighting //Code Highlighting
$code-keyword-color: #45244a; $code-keyword-color: #45244a;
$code-source-color: #4c588c; $code-source-color: #4c588c;
$code-constant-color: scale-color(magenta,$lightness:-5%); $code-constant-color: scale-color(magenta, $lightness: -5%);
$code-operator-color: scale-color($code-source-color,$lightness:-5%); $code-operator-color: scale-color($code-source-color, $lightness: -5%);
$code-string-color: #8a571c; $code-string-color: #8a571c;
$code-comment-color: #1c4c8a; $code-comment-color: #1c4c8a;
+11 -9
View File
@@ -1,12 +1,14 @@
/* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */ /* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */
@import url('./feather.css'); @import url("./feather.css");
@import url('./fonts/Route159/Route159.css'); @import url("./fonts/Route159/Route159.css");
@import url('./fonts/Lora/Lora.css'); @import url("./fonts/Lora/Lora.css");
@import url('./fonts/Playfair_Display/PlayfairDisplay.css'); @import url("./fonts/Playfair_Display/PlayfairDisplay.css");
@import url("./fonts/Vazir_WOL/Vazir_WOL.css");
@import url("./fonts/Shabnam_WOL/Shabnam_WOL.css");
@import 'dark_variables'; @import "dark_variables";
@import 'global'; @import "global";
@import 'header'; @import "header";
@import 'article'; @import "article";
@import 'forms'; @import "forms";
@@ -0,0 +1,94 @@
Copyright (c) 2015, Saber Rastikerdar (saber.rastikerdar@gmail.com),
Glyphs and data from Roboto font are licensed under the Apache License, Version 2.0.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,49 @@
@font-face {
font-family: Shabnam;
src: url("Shabnam-WOL.eot");
src: url("Shabnam-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-WOL.woff2") format("woff2"),
url("Shabnam-WOL.woff") format("woff"),
url("Shabnam-WOL.ttf") format("truetype");
font-weight: normal;
}
@font-face {
font-family: Shabnam;
src: url("Shabnam-Bold-WOL.eot");
src: url("Shabnam-Bold-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-Bold-WOL.woff2") format("woff2"),
url("Shabnam-Bold-WOL.woff") format("woff"),
url("Shabnam-Bold-WOL.ttf") format("truetype");
font-weight: bold;
}
@font-face {
font-family: Shabnam;
src: url("Shabnam-Thin-WOL.eot");
src: url("Shabnam-Thin-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-Thin-WOL.woff2") format("woff2"),
url("Shabnam-Thin-WOL.woff") format("woff"),
url("Shabnam-Thin-WOL.ttf") format("truetype");
font-weight: 100;
}
@font-face {
font-family: Shabnam;
src: url("Shabnam-Light-WOL.eot");
src: url("Shabnam-Light-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-Light-WOL.woff2") format("woff2"),
url("Shabnam-Light-WOL.woff") format("woff"),
url("Shabnam-Light-WOL.ttf") format("truetype");
font-weight: 300;
}
@font-face {
font-family: Shabnam;
src: url("Shabnam-Medium-WOL.eot");
src: url("Shabnam-Medium-WOL.eot?#iefix") format("embedded-opentype"),
url("Shabnam-Medium-WOL.woff2") format("woff2"),
url("Shabnam-Medium-WOL.woff") format("woff"),
url("Shabnam-Medium-WOL.ttf") format("truetype");
font-weight: 500;
}
@@ -0,0 +1,51 @@
Changes by Saber Rastikerdar (saber.rastikerdar@gmail.com) are in public domain.
Glyphs and data from Roboto font are licensed under the Apache License, Version 2.0.
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Bitstream Vera Fonts Copyright
------------------------------
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words "Bitstream" or the word
"Vera".
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the "Bitstream
Vera" names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,65 @@
@font-face {
font-family: Vazir;
src: url('Vazir-WOL.eot');
src: url('Vazir-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-WOL.woff2') format('woff2'),
url('Vazir-WOL.woff') format('woff'),
url('Vazir-WOL.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Bold-WOL.eot');
src: url('Vazir-Bold-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Bold-WOL.woff2') format('woff2'),
url('Vazir-Bold-WOL.woff') format('woff'),
url('Vazir-Bold-WOL.ttf') format('truetype');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Black-WOL.eot');
src: url('Vazir-Black-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Black-WOL.woff2') format('woff2'),
url('Vazir-Black-WOL.woff') format('woff'),
url('Vazir-Black-WOL.ttf') format('truetype');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Medium-WOL.eot');
src: url('Vazir-Medium-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Medium-WOL.woff2') format('woff2'),
url('Vazir-Medium-WOL.woff') format('woff'),
url('Vazir-Medium-WOL.ttf') format('truetype');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Light-WOL.eot');
src: url('Vazir-Light-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Light-WOL.woff2') format('woff2'),
url('Vazir-Light-WOL.woff') format('woff'),
url('Vazir-Light-WOL.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: Vazir;
src: url('Vazir-Thin-WOL.eot');
src: url('Vazir-Thin-WOL.eot?#iefix') format('embedded-opentype'),
url('Vazir-Thin-WOL.woff2') format('woff2'),
url('Vazir-Thin-WOL.woff') format('woff'),
url('Vazir-Thin-WOL.ttf') format('truetype');
font-weight: 100;
font-style: normal;
}
+11 -9
View File
@@ -1,12 +1,14 @@
/* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */ /* color palette: https://coolors.co/23f0c7-ef767a-7765e3-6457a6-ffe347 */
@import url('./feather.css'); @import url("./feather.css");
@import url('./fonts/Route159/Route159.css'); @import url("./fonts/Route159/Route159.css");
@import url('./fonts/Lora/Lora.css'); @import url("./fonts/Lora/Lora.css");
@import url('./fonts/Playfair_Display/PlayfairDisplay.css'); @import url("./fonts/Playfair_Display/PlayfairDisplay.css");
@import url("./fonts/Vazir_WOL/Vazir_WOL.css");
@import url("./fonts/Shabnam_WOL/Shabnam_WOL.css");
@import 'variables'; @import "variables";
@import 'global'; @import "global";
@import 'header'; @import "header";
@import 'article'; @import "article";
@import 'forms'; @import "forms";
+2 -2
View File
@@ -1,5 +1,5 @@
"project_identifier": "plume" "project_id": 352097
"api_key_env": CROWDIN_API_KEY "api_token_env": "CROWDIN_API_KEY"
preserve_hierarchy: true preserve_hierarchy: true
files: files:
- source: /po/plume/plume.pot - source: /po/plume/plume.pot
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-api" name = "plume-api"
version = "0.4.0" version = "0.6.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
+2
View File
@@ -0,0 +1,2 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+2 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-cli" name = "plume-cli"
version = "0.4.0" version = "0.6.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
@@ -23,3 +23,4 @@ path = "../plume-models"
[features] [features]
postgres = ["plume-models/postgres", "diesel/postgres"] postgres = ["plume-models/postgres", "diesel/postgres"]
sqlite = ["plume-models/sqlite", "diesel/sqlite"] sqlite = ["plume-models/sqlite", "diesel/sqlite"]
search-lindera = ["plume-models/search-lindera"]
+2
View File
@@ -0,0 +1,2 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+3 -2
View File
@@ -82,7 +82,7 @@ fn init<'a>(args: &ArgMatches<'a>, conn: &Connection) {
} }
}; };
if can_do || force { if can_do || force {
let searcher = Searcher::create(&path).unwrap(); let searcher = Searcher::create(&path, &CONFIG.search_tokenizers).unwrap();
refill(args, conn, Some(searcher)); refill(args, conn, Some(searcher));
} else { } else {
eprintln!( eprintln!(
@@ -98,7 +98,8 @@ fn refill<'a>(args: &ArgMatches<'a>, conn: &Connection, searcher: Option<Searche
Some(path) => Path::new(path).join("search_index"), Some(path) => Path::new(path).join("search_index"),
None => Path::new(&CONFIG.search_index).to_path_buf(), None => Path::new(&CONFIG.search_index).to_path_buf(),
}; };
let searcher = searcher.unwrap_or_else(|| Searcher::open(&path).unwrap()); let searcher =
searcher.unwrap_or_else(|| Searcher::open(&path, &CONFIG.search_tokenizers).unwrap());
searcher.fill(conn).expect("Couldn't import post"); searcher.fill(conn).expect("Couldn't import post");
println!("Commiting result"); println!("Commiting result");
+1 -1
View File
@@ -132,7 +132,7 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
role, role,
&bio, &bio,
email, email,
User::hash_pass(&password).expect("Couldn't hash password"), Some(User::hash_pass(&password).expect("Couldn't hash password")),
) )
.expect("Couldn't save new user"); .expect("Couldn't save new user");
} }
+4 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-common" name = "plume-common"
version = "0.4.0" version = "0.6.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
@@ -14,7 +14,7 @@ heck = "0.3.0"
hex = "0.3" hex = "0.3"
hyper = "0.12.33" hyper = "0.12.33"
openssl = "0.10.22" openssl = "0.10.22"
rocket = "0.4.0" rocket = "0.4.5"
reqwest = "0.9" reqwest = "0.9"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
@@ -30,4 +30,5 @@ version = "0.4"
[dependencies.pulldown-cmark] [dependencies.pulldown-cmark]
default-features = false default-features = false
version = "0.2.0" git = "https://git.joinplu.me/Plume/pulldown-cmark"
branch = "bidi-plume"
+2
View File
@@ -0,0 +1,2 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+25
View File
@@ -164,6 +164,11 @@ where
Some(x) => x, Some(x) => x,
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)), None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)),
}; };
if Self::is_spoofed_activity(&actor_id, &act) {
return Inbox::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)
let actor = match A::from_id( let actor = match A::from_id(
ctx, ctx,
@@ -222,6 +227,26 @@ where
Inbox::Failed(err) => Err(err), Inbox::Failed(err) => Err(err),
} }
} }
fn is_spoofed_activity(actor_id: &str, act: &serde_json::Value) -> bool {
use serde_json::Value::{Array, Object, String};
let attributed_to = act["object"].get("attributedTo");
if attributed_to.is_none() {
return false;
}
let attributed_to = attributed_to.unwrap();
match attributed_to {
Array(v) => v.iter().all(|i| match i {
String(s) => s != actor_id,
Object(obj) => obj.get("id").map_or(true, |s| s != actor_id),
_ => false,
}),
String(s) => s != actor_id,
Object(obj) => obj.get("id").map_or(true, |s| s != actor_id),
_ => false,
}
}
} }
/// Get the ActivityPub ID of a JSON value. /// Get the ActivityPub ID of a JSON value.
+51 -37
View File
@@ -1,12 +1,11 @@
use heck::CamelCase; use heck::CamelCase;
use openssl::rand::rand_bytes; use openssl::rand::rand_bytes;
use pulldown_cmark::{html, Event, 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}, response::{Flash, Redirect},
}; };
use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use syntect::html::ClassedHTMLGenerator; use syntect::html::ClassedHTMLGenerator;
use syntect::parsing::SyntaxSet; use syntect::parsing::SyntaxSet;
@@ -51,10 +50,10 @@ enum State {
fn to_inline(tag: Tag<'_>) -> Tag<'_> { fn to_inline(tag: Tag<'_>) -> Tag<'_> {
match tag { match tag {
Tag::Header(_) | Tag::Table(_) | Tag::TableHead | Tag::TableRow | Tag::TableCell => { Tag::Heading(_) | Tag::Table(_) | Tag::TableHead | Tag::TableRow | Tag::TableCell => {
Tag::Paragraph Tag::Paragraph
} }
Tag::Image(url, title) => Tag::Link(url, title), Tag::Image(typ, url, title) => Tag::Link(typ, url, title),
t => t, t => t,
} }
} }
@@ -66,21 +65,31 @@ fn highlight_code<'a>(
evt: Event<'a>, evt: Event<'a>,
) -> Option<Vec<Event<'a>>> { ) -> Option<Vec<Event<'a>>> {
match evt { match evt {
Event::Start(Tag::CodeBlock(lang)) => { Event::Start(Tag::CodeBlock(kind)) => {
if lang.is_empty() { match &kind {
Some(vec![Event::Start(Tag::CodeBlock(lang))]) CodeBlockKind::Fenced(lang) if !lang.is_empty() => {
} else { *context = Some(HighlighterContext { content: vec![] });
*context = Some(HighlighterContext { content: vec![] }); }
Some(vec![Event::Start(Tag::CodeBlock(lang))]) _ => {}
} }
Some(vec![Event::Start(Tag::CodeBlock(kind))])
} }
Event::End(Tag::CodeBlock(x)) => { Event::End(Tag::CodeBlock(kind)) => {
let mut result = vec![]; let mut result = vec![];
if let Some(ctx) = context.take() { if let Some(ctx) = context.take() {
let lang = if let CodeBlockKind::Fenced(lang) = &kind {
if lang.is_empty() {
unreachable!();
} else {
lang
}
} else {
unreachable!();
};
let syntax_set = SyntaxSet::load_defaults_newlines(); let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = syntax_set.find_syntax_by_token(&x).unwrap_or_else(|| { let syntax = syntax_set.find_syntax_by_token(&lang).unwrap_or_else(|| {
syntax_set syntax_set
.find_syntax_by_name(&x) .find_syntax_by_name(&lang)
.unwrap_or_else(|| syntax_set.find_syntax_plain_text()) .unwrap_or_else(|| syntax_set.find_syntax_plain_text())
}); });
let mut html = ClassedHTMLGenerator::new(&syntax, &syntax_set); let mut html = ClassedHTMLGenerator::new(&syntax, &syntax_set);
@@ -90,7 +99,7 @@ fn highlight_code<'a>(
let q = html.finalize(); let q = html.finalize();
result.push(Event::Html(q.into())); result.push(Event::Html(q.into()));
} }
result.push(Event::End(Tag::CodeBlock(x))); result.push(Event::End(Tag::CodeBlock(kind)));
*context = None; *context = None;
Some(result) Some(result)
} }
@@ -113,10 +122,10 @@ fn flatten_text<'a>(state: &mut Option<String>, evt: Event<'a>) -> Option<Vec<Ev
prev_txt.push_str(&txt); prev_txt.push_str(&txt);
(Some(prev_txt), vec![]) (Some(prev_txt), vec![])
} }
None => (Some(txt.into_owned()), vec![]), None => (Some(txt.into_string()), vec![]),
}, },
e => match state.take() { e => match state.take() {
Some(prev) => (None, vec![Event::Text(Cow::Owned(prev)), e]), Some(prev) => (None, vec![Event::Text(CowStr::Boxed(prev.into())), e]),
None => (None, vec![e]), None => (None, vec![e]),
}, },
}; };
@@ -156,42 +165,45 @@ fn process_image<'a, 'b>(
) -> Event<'a> { ) -> Event<'a> {
if let Some(ref processor) = *processor { if let Some(ref processor) = *processor {
match evt { match evt {
Event::Start(Tag::Image(id, title)) => { Event::Start(Tag::Image(typ, id, title)) => {
if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) { if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) {
if let (Some(cw), false) = (cw, inline) { if let (Some(cw), false) = (cw, inline) {
// there is a cw, and where are not inline // there is a cw, and where are not inline
Event::Html(Cow::Owned(format!( Event::Html(CowStr::Boxed(
r#"<label for="postcontent-cw-{id}"> format!(
r#"<label for="postcontent-cw-{id}">
<input type="checkbox" id="postcontent-cw-{id}" checked="checked" class="cw-checkbox"> <input type="checkbox" id="postcontent-cw-{id}" checked="checked" class="cw-checkbox">
<span class="cw-container"> <span class="cw-container">
<span class="cw-text"> <span class="cw-text">
{cw} {cw}
</span> </span>
<img src="{url}" alt=""#, <img src="{url}" alt=""#,
id = random_hex(), id = random_hex(),
cw = cw, cw = cw,
url = url url = url
))) )
.into(),
))
} else { } else {
Event::Start(Tag::Image(Cow::Owned(url), title)) Event::Start(Tag::Image(typ, CowStr::Boxed(url.into()), title))
} }
} else { } else {
Event::Start(Tag::Image(id, title)) Event::Start(Tag::Image(typ, id, title))
} }
} }
Event::End(Tag::Image(id, title)) => { Event::End(Tag::Image(typ, id, title)) => {
if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) { if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) {
if inline || cw.is_none() { if inline || cw.is_none() {
Event::End(Tag::Image(Cow::Owned(url), title)) Event::End(Tag::Image(typ, CowStr::Boxed(url.into()), title))
} else { } else {
Event::Html(Cow::Borrowed( Event::Html(CowStr::Borrowed(
r#""/> r#""/>
</span> </span>
</label>"#, </label>"#,
)) ))
} }
} else { } else {
Event::End(Tag::Image(id, title)) Event::End(Tag::Image(typ, id, title))
} }
} }
e => e, e => e,
@@ -231,19 +243,19 @@ pub fn md_to_html<'a>(
// Ignore headings, images, and tables if inline = true // Ignore headings, images, and tables if inline = true
.scan((vec![], inline), inline_tags) .scan((vec![], inline), inline_tags)
.scan(&mut DocumentContext::default(), |ctx, evt| match evt { .scan(&mut DocumentContext::default(), |ctx, evt| match evt {
Event::Start(Tag::CodeBlock(_)) | Event::Start(Tag::Code) => { Event::Start(Tag::CodeBlock(_)) => {
ctx.in_code = true; ctx.in_code = true;
Some((vec![evt], vec![], vec![])) Some((vec![evt], vec![], vec![]))
} }
Event::End(Tag::CodeBlock(_)) | Event::End(Tag::Code) => { Event::End(Tag::CodeBlock(_)) => {
ctx.in_code = false; ctx.in_code = false;
Some((vec![evt], vec![], vec![])) Some((vec![evt], vec![], vec![]))
} }
Event::Start(Tag::Link(_, _)) => { Event::Start(Tag::Link(_, _, _)) => {
ctx.in_link = true; ctx.in_link = true;
Some((vec![evt], vec![], vec![])) Some((vec![evt], vec![], vec![]))
} }
Event::End(Tag::Link(_, _)) => { Event::End(Tag::Link(_, _, _)) => {
ctx.in_link = false; ctx.in_link = false;
Some((vec![evt], vec![], vec![])) Some((vec![evt], vec![], vec![]))
} }
@@ -264,6 +276,7 @@ pub fn md_to_html<'a>(
let mention = text_acc; let mention = text_acc;
let short_mention = mention.splitn(1, '@').next().unwrap_or(""); let short_mention = mention.splitn(1, '@').next().unwrap_or("");
let link = Tag::Link( let link = Tag::Link(
LinkType::Inline,
format!("{}@/{}/", base_url, &mention).into(), format!("{}@/{}/", base_url, &mention).into(),
short_mention.to_owned().into(), short_mention.to_owned().into(),
); );
@@ -294,8 +307,8 @@ pub fn md_to_html<'a>(
} }
let hashtag = text_acc; let hashtag = text_acc;
let link = Tag::Link( let link = Tag::Link(
format!("{}tag/{}", base_url, &hashtag.to_camel_case()) LinkType::Inline,
.into(), format!("{}tag/{}", base_url, &hashtag).into(),
hashtag.to_owned().into(), hashtag.to_owned().into(),
); );
@@ -442,6 +455,7 @@ mod tests {
("not_a#hashtag", vec![]), ("not_a#hashtag", vec![]),
("#نرم‌افزار_آزاد", vec!["نرم‌افزار_آزاد"]), ("#نرم‌افزار_آزاد", vec!["نرم‌افزار_آزاد"]),
("[#hash in link](https://example.org/)", vec![]), ("[#hash in link](https://example.org/)", vec![]),
("#zwsp\u{200b}inhash", vec!["zwsp"]),
]; ];
for (md, mentions) in tests { for (md, mentions) in tests {
@@ -459,11 +473,11 @@ mod tests {
fn test_inline() { fn test_inline() {
assert_eq!( assert_eq!(
md_to_html("# Hello", None, false, None).0, md_to_html("# Hello", None, false, None).0,
String::from("<h1>Hello</h1>\n") String::from("<h1 dir=\"auto\">Hello</h1>\n")
); );
assert_eq!( assert_eq!(
md_to_html("# Hello", None, true, None).0, md_to_html("# Hello", None, true, None).0,
String::from("<p>Hello</p>\n") String::from("<p dir=\"auto\">Hello</p>\n")
); );
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-front" name = "plume-front"
version = "0.4.0" version = "0.6.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
+2
View File
@@ -0,0 +1,2 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+7 -1
View File
@@ -269,7 +269,13 @@ pub fn init() -> Result<(), EditorError> {
let editor_button = document().create_element("a")?; let editor_button = document().create_element("a")?;
js! { @{&editor_button}.href = "#"; } js! { @{&editor_button}.href = "#"; }
editor_button.add_event_listener(|_: ClickEvent| { editor_button.add_event_listener(|_: ClickEvent| {
window().local_storage().remove("basic-editor"); if window()
.local_storage()
.insert("basic-editor", "false")
.is_err()
{
console!(log, "Failed to write into local storage");
}
window().history().go(0).ok(); // refresh window().history().go(0).ok(); // refresh
}); });
editor_button.append_child( editor_button.append_child(
+1
View File
@@ -19,6 +19,7 @@ init_i18n!(
en, en,
eo, eo,
es, es,
fa,
fr, fr,
gl, gl,
hi, hi,
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-macro" name = "plume-macro"
version = "0.4.0" version = "0.6.0"
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"
+2
View File
@@ -0,0 +1,2 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+6 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "plume-models" name = "plume-models"
version = "0.4.0" version = "0.6.0"
authors = ["Plume contributors"] authors = ["Plume contributors"]
edition = "2018" edition = "2018"
@@ -13,16 +13,17 @@ guid-create = "0.1"
heck = "0.3.0" heck = "0.3.0"
itertools = "0.8.0" itertools = "0.8.0"
lazy_static = "1.0" lazy_static = "1.0"
ldap3 = "0.7.1"
migrations_internals= "1.4.0" migrations_internals= "1.4.0"
openssl = "0.10.22" openssl = "0.10.22"
rocket = "0.4.0" rocket = "0.4.5"
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" } rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
reqwest = "0.9" reqwest = "0.9"
scheduled-thread-pool = "0.2.2" scheduled-thread-pool = "0.2.2"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
tantivy = "0.10.1" tantivy = "0.12.0"
url = "2.1" url = "2.1"
walkdir = "2.2" walkdir = "2.2"
webfinger = "0.4.1" webfinger = "0.4.1"
@@ -30,6 +31,7 @@ whatlang = "0.7.1"
shrinkwraprs = "0.2.1" shrinkwraprs = "0.2.1"
diesel-derive-newtype = "0.1.2" diesel-derive-newtype = "0.1.2"
glob = "0.3.0" glob = "0.3.0"
lindera-tantivy = { version = "0.1.3", optional = true }
[dependencies.chrono] [dependencies.chrono]
features = ["serde"] features = ["serde"]
@@ -54,3 +56,4 @@ diesel_migrations = "1.3.0"
[features] [features]
postgres = ["diesel/postgres", "plume-macro/postgres" ] postgres = ["diesel/postgres", "plume-macro/postgres" ]
sqlite = ["diesel/sqlite", "plume-macro/sqlite" ] sqlite = ["diesel/sqlite", "plume-macro/sqlite" ]
search-lindera = ["lindera-tantivy"]
Binary file not shown.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
pre-release-hook = ["cargo", "fmt"]
pre-release-replacements = []
+5 -2
View File
@@ -498,6 +498,7 @@ pub(crate) mod tests {
use super::*; use super::*;
use crate::{ use crate::{
blog_authors::*, blog_authors::*,
config::CONFIG,
instance::tests as instance_tests, instance::tests as instance_tests,
medias::NewMedia, medias::NewMedia,
search::tests::get_searcher, search::tests::get_searcher,
@@ -767,7 +768,9 @@ pub(crate) mod tests {
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let (_, blogs) = fill_database(conn); let (_, blogs) = fill_database(conn);
blogs[0].delete(conn, &get_searcher()).unwrap(); blogs[0]
.delete(conn, &get_searcher(&CONFIG.search_tokenizers))
.unwrap();
assert!(Blog::get(conn, blogs[0].id).is_err()); assert!(Blog::get(conn, blogs[0].id).is_err());
Ok(()) Ok(())
}) })
@@ -777,7 +780,7 @@ pub(crate) mod tests {
fn delete_via_user() { fn delete_via_user() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let searcher = get_searcher(); let searcher = get_searcher(&CONFIG.search_tokenizers);
let (user, _) = fill_database(conn); let (user, _) = fill_database(conn);
let b1 = Blog::insert( let b1 = Blog::insert(
+91
View File
@@ -1,3 +1,4 @@
use crate::search::TokenizerKind as SearchTokenizer;
use rocket::config::Limits; use rocket::config::Limits;
use rocket::Config as RocketConfig; use rocket::Config as RocketConfig;
use std::env::{self, var}; use std::env::{self, var};
@@ -14,10 +15,12 @@ pub struct Config {
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 search_index: String, pub search_index: String,
pub search_tokenizers: SearchTokenizerConfig,
pub rocket: Result<RocketConfig, RocketError>, pub rocket: Result<RocketConfig, RocketError>,
pub logo: LogoConfig, pub logo: LogoConfig,
pub default_theme: String, pub default_theme: String,
pub media_directory: String, pub media_directory: String,
pub ldap: Option<LdapConfig>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -188,6 +191,92 @@ impl Default for LogoConfig {
} }
} }
pub struct SearchTokenizerConfig {
pub tag_tokenizer: SearchTokenizer,
pub content_tokenizer: SearchTokenizer,
pub property_tokenizer: SearchTokenizer,
}
impl SearchTokenizerConfig {
pub fn init() -> Self {
use SearchTokenizer::*;
match var("SEARCH_LANG").ok().as_deref() {
Some("ja") => {
#[cfg(not(feature = "search-lindera"))]
panic!("You need build Plume with search-lindera feature, or execute it with SEARCH_TAG_TOKENIZER=ngram and SEARCH_CONTENT_TOKENIZER=ngram to enable Japanese search feature");
#[cfg(feature = "search-lindera")]
Self {
tag_tokenizer: Self::determine_tokenizer("SEARCH_TAG_TOKENIZER", Lindera),
content_tokenizer: Self::determine_tokenizer(
"SEARCH_CONTENT_TOKENIZER",
Lindera,
),
property_tokenizer: Ngram,
}
}
_ => Self {
tag_tokenizer: Self::determine_tokenizer("SEARCH_TAG_TOKENIZER", Whitespace),
content_tokenizer: Self::determine_tokenizer("SEARCH_CONTENT_TOKENIZER", Simple),
property_tokenizer: Ngram,
},
}
}
fn determine_tokenizer(var_name: &str, default: SearchTokenizer) -> SearchTokenizer {
use SearchTokenizer::*;
match var(var_name).ok().as_deref() {
Some("simple") => Simple,
Some("ngram") => Ngram,
Some("whitespace") => Whitespace,
Some("lindera") => {
#[cfg(not(feature = "search-lindera"))]
panic!("You need build Plume with search-lindera feature to use Lindera tokenizer");
#[cfg(feature = "search-lindera")]
Lindera
}
_ => default,
}
}
}
pub struct LdapConfig {
pub addr: String,
pub base_dn: String,
pub tls: bool,
pub user_name_attr: String,
pub mail_attr: String,
}
fn get_ldap_config() -> Option<LdapConfig> {
let addr = var("LDAP_ADDR").ok();
let base_dn = var("LDAP_BASE_DN").ok();
match (addr, base_dn) {
(Some(addr), Some(base_dn)) => {
let tls = var("LDAP_TLS").unwrap_or_else(|_| "false".to_owned());
let tls = match tls.as_ref() {
"1" | "true" | "TRUE" => true,
"0" | "false" | "FALSE" => false,
_ => panic!("Invalid LDAP configuration : tls"),
};
let user_name_attr = var("LDAP_USER_NAME_ATTR").unwrap_or_else(|_| "cn".to_owned());
let mail_attr = var("LDAP_USER_MAIL_ATTR").unwrap_or_else(|_| "mail".to_owned());
Some(LdapConfig {
addr,
base_dn,
tls,
user_name_attr,
mail_attr,
})
}
(None, None) => None,
(_, _) => {
panic!("Invalid LDAP configuration : both LDAP_ADDR and LDAP_BASE_DN must be set")
}
}
}
lazy_static! { lazy_static! {
pub static ref CONFIG: Config = Config { pub static ref CONFIG: Config = Config {
base_url: var("BASE_URL").unwrap_or_else(|_| format!( base_url: var("BASE_URL").unwrap_or_else(|_| format!(
@@ -209,10 +298,12 @@ lazy_static! {
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
database_url: var("DATABASE_URL").unwrap_or_else(|_| format!("{}.sqlite", DB_NAME)), database_url: var("DATABASE_URL").unwrap_or_else(|_| format!("{}.sqlite", DB_NAME)),
search_index: var("SEARCH_INDEX").unwrap_or_else(|_| "search_index".to_owned()), search_index: var("SEARCH_INDEX").unwrap_or_else(|_| "search_index".to_owned()),
search_tokenizers: SearchTokenizerConfig::init(),
rocket: get_rocket_config(), rocket: get_rocket_config(),
logo: LogoConfig::default(), logo: LogoConfig::default(),
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()),
ldap: get_ldap_config(),
}; };
} }
+202
View File
@@ -173,6 +173,97 @@ pub(crate) mod tests {
}); });
} }
#[test]
fn spoof_comment() {
let r = rockets();
let conn = &*r.conn;
conn.test_transaction::<_, (), _>(|| {
let (posts, users, _) = fill_database(&r);
let act = json!({
"id": "https://plu.me/comment/1/activity",
"actor": users[0].ap_url,
"object": {
"type": "Note",
"id": "https://plu.me/comment/1",
"attributedTo": users[1].ap_url,
"inReplyTo": posts[0].ap_url,
"content": "Hello.",
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY]
},
"type": "Create",
});
assert!(matches!(
super::inbox(&r, act.clone()),
Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
))
));
Ok(())
});
}
#[test]
fn spoof_comment_by_object_with_id() {
let r = rockets();
let conn = &*r.conn;
conn.test_transaction::<_, (), _>(|| {
let (posts, users, _) = fill_database(&r);
let act = json!({
"id": "https://plu.me/comment/1/activity",
"actor": users[0].ap_url,
"object": {
"type": "Note",
"id": "https://plu.me/comment/1",
"attributedTo": {
"id": users[1].ap_url
},
"inReplyTo": posts[0].ap_url,
"content": "Hello.",
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY]
},
"type": "Create",
});
assert!(matches!(
super::inbox(&r, act.clone()),
Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
))
));
Ok(())
});
}
#[test]
fn spoof_comment_by_object_without_id() {
let r = rockets();
let conn = &*r.conn;
conn.test_transaction::<_, (), _>(|| {
let (posts, users, _) = fill_database(&r);
let act = json!({
"id": "https://plu.me/comment/1/activity",
"actor": users[0].ap_url,
"object": {
"type": "Note",
"id": "https://plu.me/comment/1",
"attributedTo": {},
"inReplyTo": posts[0].ap_url,
"content": "Hello.",
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY]
},
"type": "Create",
});
assert!(matches!(
super::inbox(&r, act.clone()),
Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
))
));
Ok(())
});
}
#[test] #[test]
fn create_post() { fn create_post() {
let r = rockets(); let r = rockets();
@@ -214,6 +305,117 @@ pub(crate) mod tests {
}); });
} }
#[test]
fn spoof_post() {
let r = rockets();
let conn = &*r.conn;
conn.test_transaction::<_, (), _>(|| {
let (_, users, blogs) = fill_database(&r);
let act = json!({
"id": "https://plu.me/comment/1/activity",
"actor": users[0].ap_url,
"object": {
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": [users[1].ap_url, blogs[0].ap_url],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY]
},
"type": "Create",
});
assert!(matches!(
super::inbox(&r, act.clone()),
Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
))
));
Ok(())
});
}
#[test]
fn spoof_post_by_object_with_id() {
let r = rockets();
let conn = &*r.conn;
conn.test_transaction::<_, (), _>(|| {
let (_, users, blogs) = fill_database(&r);
let act = json!({
"id": "https://plu.me/comment/1/activity",
"actor": users[0].ap_url,
"object": {
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": [
{"id": users[1].ap_url},
blogs[0].ap_url
],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY]
},
"type": "Create",
});
assert!(matches!(
super::inbox(&r, act.clone()),
Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
))
));
Ok(())
});
}
#[test]
fn spoof_post_by_object_without_id() {
let r = rockets();
let conn = &*r.conn;
conn.test_transaction::<_, (), _>(|| {
let (_, users, blogs) = fill_database(&r);
let act = json!({
"id": "https://plu.me/comment/1/activity",
"actor": users[0].ap_url,
"object": {
"type": "Article",
"id": "https://plu.me/~/Blog/my-article",
"attributedTo": [{}, blogs[0].ap_url],
"content": "Hello.",
"name": "My Article",
"summary": "Bye.",
"source": {
"content": "Hello.",
"mediaType": "text/markdown"
},
"published": "2014-12-12T12:12:12Z",
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY]
},
"type": "Create",
});
assert!(matches!(
super::inbox(&r, act.clone()),
Err(super::Error::Inbox(
box plume_common::activity_pub::inbox::InboxError::InvalidObject(_),
))
));
Ok(())
});
}
#[test] #[test]
fn delete_comment() { fn delete_comment() {
use crate::comments::*; use crate::comments::*;
+2 -1
View File
@@ -1,6 +1,7 @@
#![feature(try_trait)] #![feature(try_trait)]
#![feature(never_type)] #![feature(never_type)]
#![feature(proc_macro_hygiene)] #![feature(proc_macro_hygiene)]
#![feature(box_patterns)]
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
@@ -320,7 +321,7 @@ mod tests {
pub fn rockets() -> super::PlumeRocket { pub fn rockets() -> super::PlumeRocket {
super::PlumeRocket { super::PlumeRocket {
conn: db_conn::DbConn((*DB_POOL).get().unwrap()), conn: db_conn::DbConn((*DB_POOL).get().unwrap()),
searcher: Arc::new(search::tests::get_searcher()), searcher: Arc::new(search::tests::get_searcher(&CONFIG.search_tokenizers)),
worker: Arc::new(ScheduledThreadPool::new(2)), worker: Arc::new(ScheduledThreadPool::new(2)),
user: None, user: None,
} }
+1 -3
View File
@@ -11,7 +11,7 @@ use activitypub::{
}; };
use chrono::{NaiveDateTime, TimeZone, Utc}; use chrono::{NaiveDateTime, TimeZone, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
use heck::{CamelCase, KebabCase}; use heck::KebabCase;
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsObject, FromId}, inbox::{AsObject, FromId},
@@ -622,7 +622,6 @@ impl FromId<PlumeRocket> for Post {
let mut hashtags = md_to_html(&post.source, None, false, None) let mut hashtags = md_to_html(&post.source, None, false, None)
.2 .2
.into_iter() .into_iter()
.map(|s| s.to_camel_case())
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
if let Some(serde_json::Value::Array(tags)) = article.object_props.tag { if let Some(serde_json::Value::Array(tags)) = article.object_props.tag {
for tag in tags { for tag in tags {
@@ -762,7 +761,6 @@ impl AsObject<User, Update, &PlumeRocket> for PostUpdate {
let mut txt_hashtags = md_to_html(&post.source, None, false, None) let mut txt_hashtags = md_to_html(&post.source, None, false, None)
.2 .2
.into_iter() .into_iter()
.map(|s| s.to_camel_case())
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
if let Some(serde_json::Value::Array(mention_tags)) = self.tags { if let Some(serde_json::Value::Array(mention_tags)) = self.tags {
let mut mentions = vec![]; let mut mentions = vec![];
+74 -12
View File
@@ -3,10 +3,11 @@ mod searcher;
mod tokenizer; mod tokenizer;
pub use self::query::PlumeQuery as Query; pub use self::query::PlumeQuery as Query;
pub use self::searcher::*; pub use self::searcher::*;
pub use self::tokenizer::TokenizerKind;
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use super::{Query, Searcher}; use super::{Query, Searcher, TokenizerKind};
use diesel::Connection; use diesel::Connection;
use plume_common::utils::random_hex; use plume_common::utils::random_hex;
use std::env::temp_dir; use std::env::temp_dir;
@@ -14,18 +15,20 @@ pub(crate) mod tests {
use crate::{ use crate::{
blogs::tests::fill_database, blogs::tests::fill_database,
config::SearchTokenizerConfig,
post_authors::*, post_authors::*,
posts::{NewPost, Post}, posts::{NewPost, Post},
safe_string::SafeString, safe_string::SafeString,
tests::db, tests::db,
CONFIG,
}; };
pub(crate) fn get_searcher() -> Searcher { pub(crate) fn get_searcher(tokenizers: &SearchTokenizerConfig) -> Searcher {
let dir = temp_dir().join(&format!("plume-test-{}", random_hex())); let dir = temp_dir().join(&format!("plume-test-{}", random_hex()));
if dir.exists() { if dir.exists() {
Searcher::open(&dir) Searcher::open(&dir, tokenizers)
} else { } else {
Searcher::create(&dir) Searcher::create(&dir, tokenizers)
} }
.unwrap() .unwrap()
} }
@@ -100,27 +103,27 @@ pub(crate) mod tests {
fn open() { fn open() {
let dir = temp_dir().join(format!("plume-test-{}", random_hex())); let dir = temp_dir().join(format!("plume-test-{}", random_hex()));
{ {
Searcher::create(&dir).unwrap(); Searcher::create(&dir, &CONFIG.search_tokenizers).unwrap();
} }
Searcher::open(&dir).unwrap(); Searcher::open(&dir, &CONFIG.search_tokenizers).unwrap();
} }
#[test] #[test]
fn create() { fn create() {
let dir = temp_dir().join(format!("plume-test-{}", random_hex())); let dir = temp_dir().join(format!("plume-test-{}", random_hex()));
assert!(Searcher::open(&dir).is_err()); assert!(Searcher::open(&dir, &CONFIG.search_tokenizers).is_err());
{ {
Searcher::create(&dir).unwrap(); Searcher::create(&dir, &CONFIG.search_tokenizers).unwrap();
} }
Searcher::open(&dir).unwrap(); //verify it's well created Searcher::open(&dir, &CONFIG.search_tokenizers).unwrap(); //verify it's well created
} }
#[test] #[test]
fn search() { fn search() {
let conn = &db(); let conn = &db();
conn.test_transaction::<_, (), _>(|| { conn.test_transaction::<_, (), _>(|| {
let searcher = get_searcher(); let searcher = get_searcher(&CONFIG.search_tokenizers);
let blog = &fill_database(conn).1[0]; let blog = &fill_database(conn).1[0];
let author = &blog.list_authors(conn).unwrap()[0]; let author = &blog.list_authors(conn).unwrap()[0];
@@ -180,10 +183,69 @@ pub(crate) mod tests {
}); });
} }
#[cfg(feature = "search-lindera")]
#[test]
fn search_japanese() {
let conn = &db();
conn.test_transaction::<_, (), _>(|| {
let tokenizers = SearchTokenizerConfig {
tag_tokenizer: TokenizerKind::Lindera,
content_tokenizer: TokenizerKind::Lindera,
property_tokenizer: TokenizerKind::Ngram,
};
let searcher = get_searcher(&tokenizers);
let blog = &fill_database(conn).1[0];
let title = random_hex()[..8].to_owned();
let post = Post::insert(
conn,
NewPost {
blog_id: blog.id,
slug: title.clone(),
title: title.clone(),
content: SafeString::new("ブログエンジンPlumeです。"),
published: true,
license: "CC-BY-SA".to_owned(),
ap_url: "".to_owned(),
creation_date: None,
subtitle: "".to_owned(),
source: "".to_owned(),
cover_id: None,
},
&searcher,
)
.unwrap();
searcher.commit();
assert_eq!(
searcher.search_document(conn, Query::from_str("ブログエンジン").unwrap(), (0, 1))
[0]
.id,
post.id
);
assert_eq!(
searcher.search_document(conn, Query::from_str("Plume").unwrap(), (0, 1))[0].id,
post.id
);
assert_eq!(
searcher.search_document(conn, Query::from_str("です").unwrap(), (0, 1))[0].id,
post.id
);
assert_eq!(
searcher.search_document(conn, Query::from_str("").unwrap(), (0, 1))[0].id,
post.id
);
Ok(())
});
}
#[test] #[test]
fn drop_writer() { fn drop_writer() {
let searcher = get_searcher(); let searcher = get_searcher(&CONFIG.search_tokenizers);
searcher.drop_writer(); searcher.drop_writer();
get_searcher(); get_searcher(&CONFIG.search_tokenizers);
} }
} }
+47 -39
View File
@@ -1,18 +1,14 @@
use crate::{ use crate::{
instance::Instance, config::SearchTokenizerConfig, instance::Instance, posts::Post, schema::posts,
posts::Post, search::query::PlumeQuery, tags::Tag, Connection, Result,
schema::posts,
search::{query::PlumeQuery, tokenizer},
tags::Tag,
Connection, Result,
}; };
use chrono::Datelike; use chrono::Datelike;
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl};
use itertools::Itertools; use itertools::Itertools;
use std::{cmp, fs::create_dir_all, path::Path, sync::Mutex}; use std::{cmp, fs::create_dir_all, io, path::Path, sync::Mutex};
use tantivy::{ use tantivy::{
collector::TopDocs, directory::MmapDirectory, schema::*, tokenizer::*, Index, IndexReader, collector::TopDocs, directory::MmapDirectory, schema::*, Index, IndexReader, IndexWriter,
IndexWriter, ReloadPolicy, Term, ReloadPolicy, TantivyError, Term,
}; };
use whatlang::{detect as detect_lang, Lang}; use whatlang::{detect as detect_lang, Lang};
@@ -22,6 +18,7 @@ pub enum SearcherError {
WriteLockAcquisitionError, WriteLockAcquisitionError,
IndexOpeningError, IndexOpeningError,
IndexEditionError, IndexEditionError,
InvalidIndexDataError,
} }
pub struct Searcher { pub struct Searcher {
@@ -34,7 +31,7 @@ impl Searcher {
pub fn schema() -> Schema { pub fn schema() -> Schema {
let tag_indexing = TextOptions::default().set_indexing_options( let tag_indexing = TextOptions::default().set_indexing_options(
TextFieldIndexing::default() TextFieldIndexing::default()
.set_tokenizer("whitespace_tokenizer") .set_tokenizer("tag_tokenizer")
.set_index_option(IndexRecordOption::Basic), .set_index_option(IndexRecordOption::Basic),
); );
@@ -70,15 +67,7 @@ impl Searcher {
schema_builder.build() schema_builder.build()
} }
pub fn create(path: &dyn AsRef<Path>) -> Result<Self> { pub fn create(path: &dyn AsRef<Path>, tokenizers: &SearchTokenizerConfig) -> Result<Self> {
let whitespace_tokenizer = tokenizer::WhitespaceTokenizer.filter(LowerCaser);
let content_tokenizer = SimpleTokenizer
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser);
let property_tokenizer = NgramTokenizer::new(2, 8, false).filter(LowerCaser);
let schema = Self::schema(); let schema = Self::schema();
create_dir_all(path).map_err(|_| SearcherError::IndexCreationError)?; create_dir_all(path).map_err(|_| SearcherError::IndexCreationError)?;
@@ -90,9 +79,9 @@ impl Searcher {
{ {
let tokenizer_manager = index.tokenizers(); let tokenizer_manager = index.tokenizers();
tokenizer_manager.register("whitespace_tokenizer", whitespace_tokenizer); tokenizer_manager.register("tag_tokenizer", tokenizers.tag_tokenizer);
tokenizer_manager.register("content_tokenizer", content_tokenizer); tokenizer_manager.register("content_tokenizer", tokenizers.content_tokenizer);
tokenizer_manager.register("property_tokenizer", property_tokenizer); tokenizer_manager.register("property_tokenizer", tokenizers.property_tokenizer);
} //to please the borrow checker } //to please the borrow checker
Ok(Self { Ok(Self {
writer: Mutex::new(Some( writer: Mutex::new(Some(
@@ -109,38 +98,57 @@ impl Searcher {
}) })
} }
pub fn open(path: &dyn AsRef<Path>) -> Result<Self> { pub fn open(path: &dyn AsRef<Path>, tokenizers: &SearchTokenizerConfig) -> Result<Self> {
let whitespace_tokenizer = tokenizer::WhitespaceTokenizer.filter(LowerCaser); let mut index =
let content_tokenizer = SimpleTokenizer
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser);
let property_tokenizer = NgramTokenizer::new(2, 8, false).filter(LowerCaser);
let index =
Index::open(MmapDirectory::open(path).map_err(|_| SearcherError::IndexOpeningError)?) Index::open(MmapDirectory::open(path).map_err(|_| SearcherError::IndexOpeningError)?)
.map_err(|_| SearcherError::IndexOpeningError)?; .map_err(|_| SearcherError::IndexOpeningError)?;
{ {
let tokenizer_manager = index.tokenizers(); let tokenizer_manager = index.tokenizers();
tokenizer_manager.register("whitespace_tokenizer", whitespace_tokenizer); tokenizer_manager.register("tag_tokenizer", tokenizers.tag_tokenizer);
tokenizer_manager.register("content_tokenizer", content_tokenizer); tokenizer_manager.register("content_tokenizer", tokenizers.content_tokenizer);
tokenizer_manager.register("property_tokenizer", property_tokenizer); tokenizer_manager.register("property_tokenizer", tokenizers.property_tokenizer);
} //to please the borrow checker } //to please the borrow checker
let mut writer = index let writer = index
.writer(50_000_000) .writer(50_000_000)
.map_err(|_| SearcherError::WriteLockAcquisitionError)?; .map_err(|_| SearcherError::WriteLockAcquisitionError)?;
writer
.garbage_collect_files() // Since Tantivy v0.12.0, IndexWriter::garbage_collect_files() returns Future.
// To avoid conflict with Plume async project, we don't introduce async now.
// After async is introduced to Plume, we can use garbage_collect_files() again.
// Algorithm stolen from Tantivy's SegmentUpdater::list_files()
use std::collections::HashSet;
use std::path::PathBuf;
let mut files: HashSet<PathBuf> = index
.list_all_segment_metas()
.into_iter()
.flat_map(|segment_meta| segment_meta.list_files())
.collect();
files.insert(Path::new("meta.json").to_path_buf());
index
.directory_mut()
.garbage_collect(|| files)
.map_err(|_| SearcherError::IndexEditionError)?; .map_err(|_| SearcherError::IndexEditionError)?;
Ok(Self { Ok(Self {
writer: Mutex::new(Some(writer)), writer: Mutex::new(Some(writer)),
reader: index reader: index
.reader_builder() .reader_builder()
.reload_policy(ReloadPolicy::Manual) .reload_policy(ReloadPolicy::Manual)
.try_into() .try_into()
.map_err(|_| SearcherError::IndexCreationError)?, .map_err(|e| {
if let TantivyError::IOError(err) = e {
let err: io::Error = err.into();
if err.kind() == io::ErrorKind::InvalidData {
// Search index was created in older Tantivy format.
SearcherError::InvalidIndexDataError
} else {
SearcherError::IndexCreationError
}
} else {
SearcherError::IndexCreationError
}
})?,
index, index,
}) })
} }
+34 -7
View File
@@ -1,5 +1,34 @@
#[cfg(feature = "search-lindera")]
use lindera_tantivy::tokenizer::LinderaTokenizer;
use std::str::CharIndices; use std::str::CharIndices;
use tantivy::tokenizer::{Token, TokenStream, Tokenizer}; use tantivy::tokenizer::*;
#[derive(Clone, Copy)]
pub enum TokenizerKind {
Simple,
Ngram,
Whitespace,
#[cfg(feature = "search-lindera")]
Lindera,
}
impl From<TokenizerKind> for TextAnalyzer {
fn from(tokenizer: TokenizerKind) -> TextAnalyzer {
use TokenizerKind::*;
match tokenizer {
Simple => TextAnalyzer::from(SimpleTokenizer)
.filter(RemoveLongFilter::limit(40))
.filter(LowerCaser),
Ngram => TextAnalyzer::from(NgramTokenizer::new(2, 8, false)).filter(LowerCaser),
Whitespace => TextAnalyzer::from(WhitespaceTokenizer).filter(LowerCaser),
#[cfg(feature = "search-lindera")]
Lindera => {
TextAnalyzer::from(LinderaTokenizer::new("decompose", "")).filter(LowerCaser)
}
}
}
}
/// Tokenize the text by splitting on whitespaces. Pretty much a copy of Tantivy's SimpleTokenizer, /// Tokenize the text by splitting on whitespaces. Pretty much a copy of Tantivy's SimpleTokenizer,
/// but not splitting on punctuation /// but not splitting on punctuation
@@ -12,15 +41,13 @@ pub struct WhitespaceTokenStream<'a> {
token: Token, token: Token,
} }
impl<'a> Tokenizer<'a> for WhitespaceTokenizer { impl Tokenizer for WhitespaceTokenizer {
type TokenStreamImpl = WhitespaceTokenStream<'a>; fn token_stream<'a>(&self, text: &'a str) -> BoxTokenStream<'a> {
BoxTokenStream::from(WhitespaceTokenStream {
fn token_stream(&self, text: &'a str) -> Self::TokenStreamImpl {
WhitespaceTokenStream {
text, text,
chars: text.char_indices(), chars: text.char_indices(),
token: Token::default(), token: Token::default(),
} })
} }
} }
impl<'a> WhitespaceTokenStream<'a> { impl<'a> WhitespaceTokenStream<'a> {
+2 -2
View File
@@ -406,8 +406,8 @@ impl Bool {
} }
} }
Bool::HasCover => Ok(post.cover_id.is_some()), Bool::HasCover => Ok(post.cover_id.is_some()),
Bool::Local => Ok(post.get_blog(&rocket.conn)?.is_local()), Bool::Local => Ok(post.get_blog(&rocket.conn)?.is_local() && kind == Kind::Original),
Bool::All => Ok(true), Bool::All => Ok(kind == Kind::Original),
} }
} }
} }
+131 -19
View File
@@ -1,8 +1,8 @@
use crate::{ use crate::{
ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, db_conn::DbConn, follows::Follow, ap_url, blocklisted_emails::BlocklistedEmail, blogs::Blog, config::CONFIG, db_conn::DbConn,
instance::*, medias::Media, notifications::Notification, post_authors::PostAuthor, posts::Post, follows::Follow, instance::*, medias::Media, notifications::Notification,
safe_string::SafeString, schema::users, search::Searcher, timeline::Timeline, Connection, post_authors::PostAuthor, posts::Post, safe_string::SafeString, schema::users,
Error, PlumeRocket, Result, ITEMS_PER_PAGE, search::Searcher, timeline::Timeline, Connection, Error, PlumeRocket, Result, ITEMS_PER_PAGE,
}; };
use activitypub::{ use activitypub::{
activity::Delete, activity::Delete,
@@ -14,6 +14,7 @@ use activitypub::{
use bcrypt; use bcrypt;
use chrono::{NaiveDateTime, Utc}; use chrono::{NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl}; use diesel::{self, BelongingToDsl, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
use ldap3::{LdapConn, Scope, SearchEntry};
use openssl::{ use openssl::{
hash::MessageDigest, hash::MessageDigest,
pkey::{PKey, Private}, pkey::{PKey, Private},
@@ -292,11 +293,116 @@ impl User {
bcrypt::hash(pass, 10).map_err(Error::from) bcrypt::hash(pass, 10).map_err(Error::from)
} }
pub fn auth(&self, pass: &str) -> bool { fn ldap_register(conn: &Connection, name: &str, password: &str) -> Result<User> {
self.hashed_password if CONFIG.ldap.is_none() {
.clone() return Err(Error::NotFound);
.map(|hashed| bcrypt::verify(pass, hashed.as_ref()).unwrap_or(false)) }
.unwrap_or(false) let ldap = CONFIG.ldap.as_ref().unwrap();
let mut ldap_conn = LdapConn::new(&ldap.addr).map_err(|_| Error::NotFound)?;
let ldap_name = format!("{}={},{}", ldap.user_name_attr, name, ldap.base_dn);
let bind = ldap_conn
.simple_bind(&ldap_name, password)
.map_err(|_| Error::NotFound)?;
if bind.success().is_err() {
return Err(Error::NotFound);
}
let search = ldap_conn
.search(
&ldap_name,
Scope::Base,
"(|(objectClass=person)(objectClass=user))",
vec![&ldap.mail_attr],
)
.map_err(|_| Error::NotFound)?
.success()
.map_err(|_| Error::NotFound)?;
for entry in search.0 {
let entry = SearchEntry::construct(entry);
let email = entry.attrs.get("mail").and_then(|vec| vec.first());
if let Some(email) = email {
let _ = ldap_conn.unbind();
return NewUser::new_local(
conn,
name.to_owned(),
name.to_owned(),
Role::Normal,
"",
email.to_owned(),
None,
);
}
}
let _ = ldap_conn.unbind();
Err(Error::NotFound)
}
fn ldap_login(&self, password: &str) -> bool {
if let Some(ldap) = CONFIG.ldap.as_ref() {
let mut conn = if let Ok(conn) = LdapConn::new(&ldap.addr) {
conn
} else {
return false;
};
let name = format!(
"{}={},{}",
ldap.user_name_attr, &self.username, ldap.base_dn
);
if let Ok(bind) = conn.simple_bind(&name, password) {
bind.success().is_ok()
} else {
false
}
} else {
false
}
}
pub fn login(conn: &Connection, ident: &str, password: &str) -> Result<User> {
let local_id = Instance::get_local()?.id;
let user = match User::find_by_email(conn, ident) {
Ok(user) => Ok(user),
_ => User::find_by_name(conn, ident, local_id),
}
.and_then(|u| {
if u.instance_id == local_id {
Ok(u)
} else {
Err(Error::NotFound)
}
});
match user {
Ok(user) if user.hashed_password.is_some() => {
if bcrypt::verify(password, user.hashed_password.as_ref().unwrap()).unwrap_or(false)
{
Ok(user)
} else {
Err(Error::NotFound)
}
}
Ok(user) => {
if user.ldap_login(password) {
Ok(user)
} else {
Err(Error::NotFound)
}
}
e => {
if let Ok(user) = User::ldap_register(conn, ident, password) {
return Ok(user);
}
// if no user was found, and we were unable to auto-register from ldap
// fake-verify a password, and return an error.
let other = User::get(&*conn, 1)
.expect("No user is registered")
.hashed_password;
other.map(|pass| bcrypt::verify(password, &pass));
e
}
}
} }
pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> { pub fn reset_password(&self, conn: &Connection, pass: &str) -> Result<()> {
@@ -983,7 +1089,7 @@ impl NewUser {
role: Role, role: Role,
summary: &str, summary: &str,
email: String, email: String,
password: String, password: Option<String>,
) -> Result<User> { ) -> Result<User> {
let (pub_key, priv_key) = gen_keypair(); let (pub_key, priv_key) = gen_keypair();
let instance = Instance::get_local()?; let instance = Instance::get_local()?;
@@ -1001,7 +1107,7 @@ impl NewUser {
summary: summary.to_owned(), summary: summary.to_owned(),
summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0), summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0),
email: Some(email), email: Some(email),
hashed_password: Some(password), hashed_password: password,
instance_id: instance.id, instance_id: instance.id,
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?, public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?), private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
@@ -1026,6 +1132,7 @@ impl NewUser {
pub(crate) mod tests { pub(crate) mod tests {
use super::*; use super::*;
use crate::{ use crate::{
config::CONFIG,
instance::{tests as instance_tests, Instance}, instance::{tests as instance_tests, Instance},
search::tests::get_searcher, search::tests::get_searcher,
tests::{db, rockets}, tests::{db, rockets},
@@ -1042,7 +1149,7 @@ pub(crate) mod tests {
Role::Admin, Role::Admin,
"Hello there, I'm the admin", "Hello there, I'm the admin",
"admin@example.com".to_owned(), "admin@example.com".to_owned(),
"invalid_admin_password".to_owned(), Some("invalid_admin_password".to_owned()),
) )
.unwrap(); .unwrap();
let user = NewUser::new_local( let user = NewUser::new_local(
@@ -1052,7 +1159,7 @@ pub(crate) mod tests {
Role::Normal, Role::Normal,
"Hello there, I'm no one", "Hello there, I'm no one",
"user@example.com".to_owned(), "user@example.com".to_owned(),
"invalid_user_password".to_owned(), Some("invalid_user_password".to_owned()),
) )
.unwrap(); .unwrap();
let other = NewUser::new_local( let other = NewUser::new_local(
@@ -1062,7 +1169,7 @@ pub(crate) mod tests {
Role::Normal, Role::Normal,
"Hello there, I'm someone else", "Hello there, I'm someone else",
"other@example.com".to_owned(), "other@example.com".to_owned(),
"invalid_other_password".to_owned(), Some("invalid_other_password".to_owned()),
) )
.unwrap(); .unwrap();
vec![admin, user, other] vec![admin, user, other]
@@ -1081,7 +1188,7 @@ pub(crate) mod tests {
Role::Normal, Role::Normal,
"Hello I'm a test", "Hello I'm a test",
"test@example.com".to_owned(), "test@example.com".to_owned(),
User::hash_pass("test_password").unwrap(), Some(User::hash_pass("test_password").unwrap()),
) )
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@@ -1122,7 +1229,9 @@ pub(crate) mod tests {
let inserted = fill_database(conn); let inserted = fill_database(conn);
assert!(User::get(conn, inserted[0].id).is_ok()); assert!(User::get(conn, inserted[0].id).is_ok());
inserted[0].delete(conn, &get_searcher()).unwrap(); inserted[0]
.delete(conn, &get_searcher(&CONFIG.search_tokenizers))
.unwrap();
assert!(User::get(conn, inserted[0].id).is_err()); assert!(User::get(conn, inserted[0].id).is_err());
Ok(()) Ok(())
}); });
@@ -1162,12 +1271,15 @@ pub(crate) mod tests {
Role::Normal, Role::Normal,
"Hello I'm a test", "Hello I'm a test",
"test@example.com".to_owned(), "test@example.com".to_owned(),
User::hash_pass("test_password").unwrap(), Some(User::hash_pass("test_password").unwrap()),
) )
.unwrap(); .unwrap();
assert!(test_user.auth("test_password")); assert_eq!(
assert!(!test_user.auth("other_password")); User::login(conn, "test", "test_password").unwrap().id,
test_user.id
);
assert!(User::login(conn, "test", "other_password").is_err());
Ok(()) Ok(())
}); });
} }
+18 -12
View File
@@ -3,8 +3,8 @@ 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: 2019-12-16 21:06\n" "PO-Revision-Date: 2020-12-19 09:55\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: \n"
"Language-Team: Afrikaans\n" "Language-Team: Afrikaans\n"
"Language: af_ZA\n" "Language: af_ZA\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@@ -12,46 +12,52 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: plume\n" "X-Crowdin-Project: plume\n"
"X-Crowdin-Project-ID: 352097\n"
"X-Crowdin-Language: af\n" "X-Crowdin-Language: af\n"
"X-Crowdin-File: /master/po/plume-front/plume-front.pot\n" "X-Crowdin-File: /master/po/plume-front/plume-front.pot\n"
"X-Crowdin-File-ID: 12\n"
# plume-front/src/editor.rs:115 # plume-front/src/editor.rs:189
msgid "Do you want to load the local autosave last edited at {}?"
msgstr ""
# plume-front/src/editor.rs:282
msgid "Open the rich text editor" msgid "Open the rich text editor"
msgstr "" msgstr ""
# plume-front/src/editor.rs:145 # plume-front/src/editor.rs:315
msgid "Title" msgid "Title"
msgstr "" msgstr ""
# plume-front/src/editor.rs:149 # plume-front/src/editor.rs:319
msgid "Subtitle, or summary" msgid "Subtitle, or summary"
msgstr "" msgstr ""
# plume-front/src/editor.rs:156 # plume-front/src/editor.rs:326
msgid "Write your article here. Markdown is supported." msgid "Write your article here. Markdown is supported."
msgstr "" msgstr ""
# plume-front/src/editor.rs:167 # plume-front/src/editor.rs:337
msgid "Around {} characters left" msgid "Around {} characters left"
msgstr "" msgstr ""
# plume-front/src/editor.rs:243 # plume-front/src/editor.rs:414
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
# plume-front/src/editor.rs:244 # plume-front/src/editor.rs:415
msgid "License" msgid "License"
msgstr "" msgstr ""
# plume-front/src/editor.rs:247 # plume-front/src/editor.rs:418
msgid "Cover" msgid "Cover"
msgstr "" msgstr ""
# plume-front/src/editor.rs:267 # plume-front/src/editor.rs:438
msgid "This is a draft" msgid "This is a draft"
msgstr "" msgstr ""
# plume-front/src/editor.rs:274 # plume-front/src/editor.rs:445
msgid "Publish" msgid "Publish"
msgstr "" msgstr ""

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