]> Untitled Git - lemmy.git/commitdiff
Merge branch 'nutomic-auto-setup' into dev
authorDessalines <tyhou13@gmx.com>
Tue, 31 Mar 2020 20:29:57 +0000 (16:29 -0400)
committerDessalines <tyhou13@gmx.com>
Tue, 31 Mar 2020 20:29:57 +0000 (16:29 -0400)
28 files changed:
ansible/VERSION
ansible/templates/docker-compose.yml
docker/prod/docker-compose.yml
docs/src/SUMMARY.md
docs/src/about_goals.md
docs/src/contributing_federation_development.md [new file with mode: 0644]
server/Cargo.lock
server/Cargo.toml
server/src/db/mod.rs
server/src/lib.rs
server/src/routes/feeds.rs
server/src/routes/nodeinfo.rs
server/src/settings.rs
server/src/version.rs
ui/.eslintrc.json
ui/src/components/comment-node.tsx
ui/src/components/comment-nodes.tsx
ui/src/components/community.tsx
ui/src/components/inbox.tsx
ui/src/components/main.tsx
ui/src/components/navbar.tsx
ui/src/components/post-listing.tsx
ui/src/components/post.tsx
ui/src/components/symbols.tsx
ui/src/components/user.tsx
ui/src/utils.ts
ui/src/version.ts
ui/translations/en.json

index de2652f2e26d76c4993c57441d54b48987b03413..83ca525b752826ef9d56ff543e68f6b77c68cdfc 100644 (file)
@@ -1 +1 @@
-v0.6.41
+v0.6.44
index 022b44ddabbdefb659627a1c49597fdb7efca51c..d6afd2532d9b809e395405f6ed08e8ac6b39223d 100644 (file)
@@ -7,7 +7,7 @@ services:
       - "127.0.0.1:{{ lemmy_port }}:8536"
     restart: always
     environment:
-      - RUST_LOG=debug
+      - RUST_LOG=error
     volumes:
       - ./lemmy.hjson:/config/config.hjson:ro
     depends_on:
index 3343c0d856b879ca5b0404ea77719a2b585012d9..76f5ae14de0feebc27f503e7c903ce341400e750 100644 (file)
@@ -12,12 +12,12 @@ services:
     restart: always
 
   lemmy:
-    image: dessalines/lemmy:v0.6.41
+    image: dessalines/lemmy:v0.6.44
     ports:
       - "127.0.0.1:8536:8536"
     restart: always
     environment:
-      - RUST_LOG=debug
+      - RUST_LOG=error
     volumes:
       - ./lemmy.hjson:/config/config.hjson:ro
     depends_on:
index 10a6153ea881a6718101af663f2603c593319da2..70c423c7920f953f167b73a72bae6f3e7bb9aaf2 100644 (file)
@@ -13,6 +13,7 @@
 - [Contributing](contributing.md)
   - [Docker Development](contributing_docker_development.md)
   - [Local Development](contributing_local_development.md)
+  - [Federation Development](contributing_federation_development.md)
   - [Websocket/HTTP API](contributing_websocket_http_api.md)
   - [ActivityPub API Outline](contributing_apub_api_outline.md)
   - [Theming Guide](contributing_theming.md)
index 1463eeb07a17fe454c4359de57ec68145e35d19b..caa6948a0b040e5e7896e6c566c3d3bbf62d1f8b 100644 (file)
@@ -47,3 +47,7 @@
 - https://docs.rs/activitypub/0.1.4/activitypub/
 - [Activitypub vocab.](https://www.w3.org/TR/activitystreams-vocabulary/)
 - [Activitypub main](https://www.w3.org/TR/activitypub/)
+- [Federation.md](https://github.com/dariusk/gathio/blob/7fc93dbe9d4d99457a0e85c6c532112f415b7af2/FEDERATION.md)
+- [Activitypub implementers guide](https://socialhub.activitypub.rocks/t/draft-guide-for-new-activitypub-implementers/479)
+- [Data storage questions](https://socialhub.activitypub.rocks/t/data-storage-questions/579/3)
+- [Activitypub as it has been understood](https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood)
diff --git a/docs/src/contributing_federation_development.md b/docs/src/contributing_federation_development.md
new file mode 100644 (file)
index 0000000..13a047d
--- /dev/null
@@ -0,0 +1,37 @@
+# Federation Development
+
+## Setup
+
+If you don't have a local clone of the Lemmy repo yet, just run the following command:
+
+```bash
+git clone https://yerbamate.dev/nutomic/lemmy.git -b federation
+```
+
+If you already have the Lemmy repo cloned, you need to add a new remote:
+```bash
+git remote add federation https://yerbamate.dev/nutomic/lemmy.git
+git checkout federation
+git pull federation federation
+```
+
+## Running
+
+You need to have the following packages installed, the Docker service needs to be running.
+
+- docker
+- docker-compose
+- cargo
+- yarn
+
+Then run the following
+```bash
+cd dev/federation-test
+./run-federation-test.bash
+```
+
+After the build is finished and the docker-compose setup is running, open [127.0.0.1:8540](http://127.0.0.1:8540) and
+[127.0.0.1:8541](http://127.0.0.1:8541) in your browser to use the test instances. You can login as admin with
+username `lemmy` and password `lemmy`, or create new accounts.
+
+Please get in touch if you want to contribute to this, so we can coordinate things and avoid duplicate work.
\ No newline at end of file
index f6ce6550773ab5ab3db294edffb0a98dfddbfd37..2a3bc03302b064db867ab4f09f6b8ffd832c1896 100644 (file)
@@ -378,6 +378,14 @@ dependencies = [
  "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "arc-swap"
 version = "0.4.4"
@@ -629,6 +637,20 @@ dependencies = [
  "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "clap"
+version = "2.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "cloudabi"
 version = "0.0.3"
@@ -637,6 +659,22 @@ dependencies = [
  "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "comrak"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "twoway 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "config"
 version = "0.10.1"
@@ -942,6 +980,11 @@ dependencies = [
  "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "entities"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "enum-as-inner"
 version = "0.3.2"
@@ -1377,6 +1420,7 @@ dependencies = [
  "actix-web-actors 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bcrypt 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "comrak 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "diesel 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1507,6 +1551,11 @@ dependencies = [
  "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "match_cfg"
 version = "0.1.0"
@@ -1782,6 +1831,45 @@ name = "percent-encoding"
 version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest_generator 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "pin-project"
 version = "0.4.8"
@@ -2290,6 +2378,17 @@ dependencies = [
  "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "sha1"
 version = "0.6.0"
@@ -2372,6 +2471,11 @@ name = "strsim"
 version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "strsim"
 version = "0.9.3"
@@ -2435,6 +2539,14 @@ dependencies = [
  "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.11"
@@ -2574,11 +2686,35 @@ dependencies = [
  "trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "twoway"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unchecked-index 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "typed-arena"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "typenum"
 version = "1.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "ucd-trie"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unchecked-index"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "unicase"
 version = "2.6.0"
@@ -2608,11 +2744,21 @@ name = "unicode-segmentation"
 version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "unicode-width"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "unicode_categories"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "untrusted"
 version = "0.7.0"
@@ -2674,6 +2820,11 @@ name = "vcpkg"
 version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "vec_map"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "version_check"
 version = "0.1.5"
@@ -2848,6 +2999,7 @@ dependencies = [
 "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
 "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
 "checksum aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec"
+"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
 "checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff"
 "checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
 "checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
@@ -2880,7 +3032,9 @@ dependencies = [
 "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
 "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 "checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
+"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
 "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+"checksum comrak 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c"
 "checksum config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
 "checksum copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127"
 "checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
@@ -2914,6 +3068,7 @@ dependencies = [
 "checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
 "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
 "checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28"
+"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
 "checksum enum-as-inner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c"
 "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
 "checksum failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b"
@@ -2974,6 +3129,7 @@ dependencies = [
 "checksum lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b"
 "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
 "checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
 "checksum match_cfg 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
 "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
 "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
@@ -3005,6 +3161,10 @@ dependencies = [
 "checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1"
 "checksum pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1581760c757a756a41f0ee3ff01256227bdf64cb752839779b95ffb01c59793"
 "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+"checksum pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+"checksum pest_generator 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
+"checksum pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
 "checksum pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c"
 "checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f"
 "checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
@@ -3066,6 +3226,7 @@ dependencies = [
 "checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
 "checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5"
 "checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
+"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
 "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
 "checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0"
 "checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
@@ -3077,6 +3238,7 @@ dependencies = [
 "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
 "checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c"
+"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
 "checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
 "checksum strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b"
 "checksum strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c"
@@ -3084,6 +3246,7 @@ dependencies = [
 "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
 "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
 "checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
+"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
 "checksum thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db"
 "checksum thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4"
 "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
@@ -3096,12 +3259,18 @@ dependencies = [
 "checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
 "checksum trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2a7f3a2ab8a919f5eca52a468866a67ed7d3efa265d48a652a9a3452272b413f"
 "checksum trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6f90b1502b226f8b2514c6d5b37bafa8c200d7ca4102d57dc36ee0f3b7a04a2f"
+"checksum twoway 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc"
+"checksum typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d"
 "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
+"checksum ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+"checksum unchecked-index 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
 "checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
 "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
 "checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
 "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
 "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+"checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
 "checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece"
 "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
 "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
@@ -3110,6 +3279,7 @@ dependencies = [
 "checksum v_escape_derive 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae"
 "checksum v_htmlescape 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41"
 "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
+"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
 "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
 "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
 "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
index 156ccb87a5c783edd0f582fc2fc6e0de93728516..5a4fdcece840fbddb6817e819c3c61037a09133f 100644 (file)
@@ -36,3 +36,4 @@ config = "0.10.1"
 hjson = "0.8.2"
 percent-encoding = "2.1.0"
 isahc = "0.9"
+comrak = "0.7"
index dacdb6f6a92bc2b8d72ee5366a3e1a026881cbaf..e0d358ffe7e286915493a024673d8126e5b571c8 100644 (file)
@@ -1,4 +1,3 @@
-extern crate lazy_static;
 use crate::settings::Settings;
 use diesel::dsl::*;
 use diesel::result::Error;
index 01f01ff1072f8ab6795c68a5ca455d3654d4909a..8257dab9a6720a126143090915da494a977d0552 100644 (file)
@@ -11,12 +11,14 @@ pub extern crate actix;
 pub extern crate actix_web;
 pub extern crate bcrypt;
 pub extern crate chrono;
+pub extern crate comrak;
 pub extern crate dotenv;
 pub extern crate jsonwebtoken;
 pub extern crate lettre;
 pub extern crate lettre_email;
 pub extern crate rand;
 pub extern crate regex;
+pub extern crate rss;
 pub extern crate serde;
 pub extern crate serde_json;
 pub extern crate sha2;
@@ -218,6 +220,10 @@ fn fetch_iframely_and_pictshare_data(
   )
 }
 
+pub fn markdown_to_html(text: &str) -> String {
+  comrak::markdown_to_html(text, &comrak::ComrakOptions::default())
+}
+
 #[cfg(test)]
 mod tests {
   use crate::{extract_usernames, is_email_regex, remove_slurs, slur_check, slurs_vec_to_str};
index ad0f28d5a14c8f9fbdec8f09cb8f89f9199aa444..6826cf5cb4221c7febd3fbfb3b2735cd7e43d9bf 100644 (file)
@@ -1,5 +1,3 @@
-extern crate rss;
-
 use super::*;
 use crate::db::comment_view::{ReplyQueryBuilder, ReplyView};
 use crate::db::community::Community;
@@ -8,9 +6,9 @@ use crate::db::site_view::SiteView;
 use crate::db::user::{Claims, User_};
 use crate::db::user_mention_view::{UserMentionQueryBuilder, UserMentionView};
 use crate::db::{ListingType, SortType};
-use crate::Settings;
+use crate::{markdown_to_html, Settings};
 use actix_web::{web, HttpResponse, Result};
-use chrono::{DateTime, Utc};
+use chrono::{DateTime, NaiveDateTime, Utc};
 use diesel::r2d2::{ConnectionManager, Pool};
 use diesel::PgConnection;
 use failure::Error;
@@ -34,7 +32,6 @@ enum RequestType {
 pub fn config(cfg: &mut web::ServiceConfig) {
   cfg
     .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed))
-    .route("/feeds/all.xml", web::get().to(feeds::get_all_feed))
     .route("/feeds/all.xml", web::get().to(feeds::get_all_feed));
 }
 
@@ -44,9 +41,7 @@ async fn get_all_feed(
 ) -> Result<HttpResponse, actix_web::Error> {
   let res = web::block(move || {
     let conn = db.get()?;
-
-    let sort_type = get_sort_type(info)?;
-    get_feed_all_data(&conn, &sort_type)
+    get_feed_all_data(&conn, &get_sort_type(info)?)
   })
   .await
   .map(|rss| {
@@ -58,6 +53,29 @@ async fn get_all_feed(
   Ok(res)
 }
 
+fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String, failure::Error> {
+  let site_view = SiteView::read(&conn)?;
+
+  let posts = PostQueryBuilder::create(&conn)
+    .listing_type(ListingType::All)
+    .sort(sort_type)
+    .list()?;
+
+  let items = create_post_items(posts);
+
+  let mut channel_builder = ChannelBuilder::default();
+  channel_builder
+    .title(&format!("{} - All", site_view.name))
+    .link(format!("https://{}", Settings::get().hostname))
+    .items(items);
+
+  if let Some(site_desc) = site_view.description {
+    channel_builder.description(&site_desc);
+  }
+
+  Ok(channel_builder.build().unwrap().to_string())
+}
+
 async fn get_feed(
   path: web::Path<(String, String)>,
   info: web::Query<Params>,
@@ -86,6 +104,7 @@ async fn get_feed(
     }
   })
   .await
+  .map(|builder| builder.build().unwrap().to_string())
   .map(|rss| {
     HttpResponse::Ok()
       .content_type("application/rss+xml")
@@ -103,34 +122,11 @@ fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> {
   SortType::from_str(&sort_query)
 }
 
-fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result<String, failure::Error> {
-  let site_view = SiteView::read(&conn)?;
-
-  let posts = PostQueryBuilder::create(&conn)
-    .listing_type(ListingType::All)
-    .sort(sort_type)
-    .list()?;
-
-  let items = create_post_items(posts);
-
-  let mut channel_builder = ChannelBuilder::default();
-  channel_builder
-    .title(&format!("{} - All", site_view.name))
-    .link(format!("https://{}", Settings::get().hostname))
-    .items(items);
-
-  if let Some(site_desc) = site_view.description {
-    channel_builder.description(&site_desc);
-  }
-
-  Ok(channel_builder.build().unwrap().to_string())
-}
-
 fn get_feed_user(
   conn: &PgConnection,
   sort_type: &SortType,
   user_name: String,
-) -> Result<String, Error> {
+) -> Result<ChannelBuilder, Error> {
   let site_view = SiteView::read(&conn)?;
   let user = User_::find_by_username(&conn, &user_name)?;
   let user_url = user.get_profile_url();
@@ -149,14 +145,14 @@ fn get_feed_user(
     .link(user_url)
     .items(items);
 
-  Ok(channel_builder.build().unwrap().to_string())
+  Ok(channel_builder)
 }
 
 fn get_feed_community(
   conn: &PgConnection,
   sort_type: &SortType,
   community_name: String,
-) -> Result<String, Error> {
+) -> Result<ChannelBuilder, Error> {
   let site_view = SiteView::read(&conn)?;
   let community = Community::read_from_name(&conn, community_name)?;
   let community_url = community.get_url();
@@ -179,10 +175,14 @@ fn get_feed_community(
     channel_builder.description(&community_desc);
   }
 
-  Ok(channel_builder.build().unwrap().to_string())
+  Ok(channel_builder)
 }
 
-fn get_feed_front(conn: &PgConnection, sort_type: &SortType, jwt: String) -> Result<String, Error> {
+fn get_feed_front(
+  conn: &PgConnection,
+  sort_type: &SortType,
+  jwt: String,
+) -> Result<ChannelBuilder, Error> {
   let site_view = SiteView::read(&conn)?;
   let user_id = Claims::decode(&jwt)?.claims.id;
 
@@ -204,10 +204,10 @@ fn get_feed_front(conn: &PgConnection, sort_type: &SortType, jwt: String) -> Res
     channel_builder.description(&site_desc);
   }
 
-  Ok(channel_builder.build().unwrap().to_string())
+  Ok(channel_builder)
 }
 
-fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<String, Error> {
+fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<ChannelBuilder, Error> {
   let site_view = SiteView::read(&conn)?;
   let user_id = Claims::decode(&jwt)?.claims.id;
 
@@ -233,86 +233,61 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result<String, Error> {
     channel_builder.description(&site_desc);
   }
 
-  Ok(channel_builder.build().unwrap().to_string())
+  Ok(channel_builder)
 }
 
 fn create_reply_and_mention_items(
   replies: Vec<ReplyView>,
   mentions: Vec<UserMentionView>,
 ) -> Vec<Item> {
-  let mut items: Vec<Item> = Vec::new();
-
-  for r in replies {
-    let mut i = ItemBuilder::default();
-
-    i.title(format!("Reply from {}", r.creator_name));
-
-    let author_url = format!("https://{}/u/{}", Settings::get().hostname, r.creator_name);
-    i.author(format!(
-      "/u/{} <a href=\"{}\">(link)</a>",
-      r.creator_name, author_url
-    ));
-
-    let dt = DateTime::<Utc>::from_utc(r.published, Utc);
-    i.pub_date(dt.to_rfc2822());
-
-    let reply_url = format!(
-      "https://{}/post/{}/comment/{}",
-      Settings::get().hostname,
-      r.post_id,
-      r.id
-    );
-    i.comments(reply_url.to_owned());
-    let guid = GuidBuilder::default()
-      .permalink(true)
-      .value(&reply_url)
-      .build();
-    i.guid(guid.unwrap());
-
-    i.link(reply_url);
-
-    // TODO find a markdown to html parser here, do images, etc
-    i.description(r.content);
-
-    items.push(i.build().unwrap());
-  }
-
-  for m in mentions {
-    let mut i = ItemBuilder::default();
-
-    i.title(format!("Mention from {}", m.creator_name));
-
-    let author_url = format!("https://{}/u/{}", Settings::get().hostname, m.creator_name);
-    i.author(format!(
-      "/u/{} <a href=\"{}\">(link)</a>",
-      m.creator_name, author_url
-    ));
-
-    let dt = DateTime::<Utc>::from_utc(m.published, Utc);
-    i.pub_date(dt.to_rfc2822());
-
-    let mention_url = format!(
-      "https://{}/post/{}/comment/{}",
-      Settings::get().hostname,
-      m.post_id,
-      m.id
-    );
-    i.comments(mention_url.to_owned());
-    let guid = GuidBuilder::default()
-      .permalink(true)
-      .value(&mention_url)
-      .build();
-    i.guid(guid.unwrap());
-
-    i.link(mention_url);
-
-    // TODO find a markdown to html parser here, do images, etc
-    i.description(m.content);
-
-    items.push(i.build().unwrap());
-  }
+  let mut reply_items: Vec<Item> = replies
+    .iter()
+    .map(|r| {
+      let reply_url = format!(
+        "https://{}/post/{}/comment/{}",
+        Settings::get().hostname,
+        r.post_id,
+        r.id
+      );
+      build_item(&r.creator_name, &r.published, &reply_url, &r.content)
+    })
+    .collect();
+
+  let mut mention_items: Vec<Item> = mentions
+    .iter()
+    .map(|m| {
+      let mention_url = format!(
+        "https://{}/post/{}/comment/{}",
+        Settings::get().hostname,
+        m.post_id,
+        m.id
+      );
+      build_item(&m.creator_name, &m.published, &mention_url, &m.content)
+    })
+    .collect();
+
+  reply_items.append(&mut mention_items);
+  reply_items
+}
 
-  items
+fn build_item(creator_name: &str, published: &NaiveDateTime, url: &str, content: &str) -> Item {
+  let mut i = ItemBuilder::default();
+  i.title(format!("Reply from {}", creator_name));
+  let author_url = format!("https://{}/u/{}", Settings::get().hostname, creator_name);
+  i.author(format!(
+    "/u/{} <a href=\"{}\">(link)</a>",
+    creator_name, author_url
+  ));
+  let dt = DateTime::<Utc>::from_utc(*published, Utc);
+  i.pub_date(dt.to_rfc2822());
+  i.comments(url.to_owned());
+  let guid = GuidBuilder::default().permalink(true).value(url).build();
+  i.guid(guid.unwrap());
+  i.link(url.to_owned());
+  // TODO add images
+  let html = markdown_to_html(&content.to_string());
+  i.description(html);
+  i.build().unwrap()
 }
 
 fn create_post_items(posts: Vec<PostView>) -> Vec<Item> {
@@ -359,9 +334,8 @@ fn create_post_items(posts: Vec<PostView>) -> Vec<Item> {
       i.link(url);
     }
 
-    // TODO find a markdown to html parser here, do images, etc
-    let mut description = format!("
-    submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
+    // TODO add images
+    let mut description = format!("submitted by <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",
     author_url,
     p.creator_name,
     community_url,
@@ -371,7 +345,8 @@ fn create_post_items(posts: Vec<PostView>) -> Vec<Item> {
     p.number_of_comments);
 
     if let Some(body) = p.body {
-      description.push_str(&format!("<br><br>{}", body));
+      let html = markdown_to_html(&body);
+      description.push_str(&html);
     }
 
     i.description(description);
index 75568608e685d60fe05d4d68f621bc28984f7698..1263cef848076bc8fadf6e86baba98c92c3d2f70 100644 (file)
@@ -1,4 +1,3 @@
-extern crate lazy_static;
 use crate::db::site_view::SiteView;
 use crate::version;
 use crate::Settings;
index a2d61edbe5d8833ecf272368fdd38cee5ace14c7..875323e96e125c837cb52110ccd2001f115d4936 100644 (file)
@@ -1,4 +1,3 @@
-extern crate lazy_static;
 use config::{Config, ConfigError, Environment, File};
 use serde::Deserialize;
 use std::env;
index b5062ef50d9965549011768f86416e67f40cc323..1491a0a2c3d98d132384353664856a3bdd73c963 100644 (file)
@@ -1 +1 @@
-pub const VERSION: &str = "v0.6.41";
+pub const VERSION: &str = "v0.6.44";
index bee9e538f45afb306edf1db5dd408266901b1f4b..a4aecfa01922b44a4c29e0a8ddad29b55f4123bb 100644 (file)
@@ -38,6 +38,7 @@
     "inferno/no-direct-mutation-state": 0,
     "inferno/no-unknown-property": 0,
     "max-statements": 0,
+    "max-params": 0,
     "new-cap": 0,
     "no-console": 0,
     "no-duplicate-imports": 0,
index 8809c5b740853ebf32765a96de5219d85fee56f0..39f29b5f84eab110dc4bd7ca37f9915e7b923cdb 100644 (file)
@@ -66,6 +66,7 @@ interface CommentNodeProps {
   viewOnly?: boolean;
   locked?: boolean;
   markable?: boolean;
+  showContext?: boolean;
   moderators: Array<CommunityUser>;
   admins: Array<UserView>;
   // TODO is this necessary, can't I get it from the node itself?
@@ -146,89 +147,79 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
               this.props.node.comment.parent_id &&
               'ml-2'}`}
           >
-            <ul class="list-inline mb-1 text-muted small">
-              <li className="mt-1 list-inline-item">
-                <Link
-                  className="text-body font-weight-bold"
-                  to={`/u/${node.comment.creator_name}`}
-                >
-                  {node.comment.creator_avatar && showAvatars() && (
-                    <img
-                      height="32"
-                      width="32"
-                      src={pictshareAvatarThumbnail(
-                        node.comment.creator_avatar
-                      )}
-                      class="rounded-circle mr-1"
-                    />
-                  )}
-                  <span>{node.comment.creator_name}</span>
-                </Link>
-              </li>
+            <div class="d-flex flex-wrap align-items-center mb-1 mt-1 text-muted small">
+              <Link
+                className="mr-2 text-body font-weight-bold"
+                to={`/u/${node.comment.creator_name}`}
+              >
+                {node.comment.creator_avatar && showAvatars() && (
+                  <img
+                    height="32"
+                    width="32"
+                    src={pictshareAvatarThumbnail(node.comment.creator_avatar)}
+                    class="rounded-circle mr-1"
+                  />
+                )}
+                <span>{node.comment.creator_name}</span>
+              </Link>
               {this.isMod && (
-                <li className="list-inline-item badge badge-light">
+                <div className="badge badge-light d-none d-sm-inline mr-2">
                   {i18n.t('mod')}
-                </li>
+                </div>
               )}
               {this.isAdmin && (
-                <li className="list-inline-item badge badge-light">
+                <div className="badge badge-light d-none d-sm-inline mr-2">
                   {i18n.t('admin')}
-                </li>
+                </div>
               )}
               {this.isPostCreator && (
-                <li className="list-inline-item badge badge-light">
+                <div className="badge badge-light d-none d-sm-inline mr-2">
                   {i18n.t('creator')}
-                </li>
+                </div>
               )}
               {(node.comment.banned_from_community || node.comment.banned) && (
-                <li className="list-inline-item badge badge-danger">
+                <div className="badge badge-danger mr-2">
                   {i18n.t('banned')}
-                </li>
-              )}
-              <li className="list-inline-item">
-                <div
-                  className="unselectable pointer text-monospace"
-                  onClick={linkEvent(this, this.handleCommentCollapse)}
-                >
-                  {this.state.collapsed ? (
-                    <svg class="icon icon-inline">
-                      <use xlinkHref="#icon-plus-square"></use>
-                    </svg>
-                  ) : (
-                    <svg class="icon icon-inline">
-                      <use xlinkHref="#icon-minus-square"></use>
-                    </svg>
-                  )}
                 </div>
-              </li>
+              )}
               {this.props.showCommunity && (
-                <li className="list-inline-item">
-                  <span> {i18n.t('to')} </span>
-                  <Link to={`/c/${node.comment.community_name}`}>
+                <>
+                  <span class="mx-1">{i18n.t('to')}</span>
+                  <Link class="mr-2" to={`/c/${node.comment.community_name}`}>
                     {node.comment.community_name}
                   </Link>
-                </li>
+                </>
               )}
-              <li className="list-inline-item">•</li>
-              <li className="list-inline-item">
-                <span
-                  className={`unselectable pointer ${this.scoreColor}`}
-                  onClick={linkEvent(node, this.handleCommentUpvote)}
-                  data-tippy-content={this.pointsTippy}
-                >
-                  <svg class="icon icon-inline mr-1">
-                    <use xlinkHref="#icon-zap"></use>
+              <div
+                className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mr-2"
+                onClick={linkEvent(this, this.handleCommentCollapse)}
+              >
+                {this.state.collapsed ? (
+                  <svg class="icon icon-inline">
+                    <use xlinkHref="#icon-plus-square"></use>
+                  </svg>
+                ) : (
+                  <svg class="icon icon-inline">
+                    <use xlinkHref="#icon-minus-square"></use>
                   </svg>
-                  {this.state.score}
-                </span>
-              </li>
-              <li className="list-inline-item">•</li>
-              <li className="list-inline-item">
-                <span>
-                  <MomentTime data={node.comment} />
-                </span>
-              </li>
-            </ul>
+                )}
+              </div>
+              <span
+                className={`unselectable pointer ${this.scoreColor}`}
+                onClick={linkEvent(node, this.handleCommentUpvote)}
+                data-tippy-content={this.pointsTippy}
+              >
+                <svg class="icon icon-inline mr-1">
+                  <use xlinkHref="#icon-zap"></use>
+                </svg>
+                <span class="mr-1">{this.state.score}</span>
+              </span>
+              <span className="mr-1">•</span>
+              <span>
+                <MomentTime data={node.comment} />
+              </span>
+            </div>
+            {/* end of user row */}
             {this.state.showEdit && (
               <CommentForm
                 node={node}
@@ -249,124 +240,107 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                     )}
                   />
                 )}
-                <ul class="list-inline mb-0 text-muted font-weight-bold small">
+                <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
+                  {this.props.showContext && this.linkBtn}
                   {this.props.markable && (
-                    <li className="list-inline-item">
+                    <button
+                      class="btn btn-link btn-animate text-muted"
+                      onClick={linkEvent(this, this.handleMarkRead)}
+                      data-tippy-content={
+                        node.comment.read
+                          ? i18n.t('mark_as_unread')
+                          : i18n.t('mark_as_read')
+                      }
+                    >
+                      {this.state.readLoading ? (
+                        this.loadingIcon
+                      ) : (
+                        <svg
+                          class={`icon icon-inline ${node.comment.read &&
+                            'text-success'}`}
+                        >
+                          <use xlinkHref="#icon-check"></use>
+                        </svg>
+                      )}
+                    </button>
+                  )}
+                  {UserService.Instance.user && !this.props.viewOnly && (
+                    <>
                       <button
-                        class="btn btn-link btn-sm btn-animate text-muted"
-                        onClick={linkEvent(this, this.handleMarkRead)}
-                        data-tippy-content={
-                          node.comment.read
-                            ? i18n.t('mark_as_unread')
-                            : i18n.t('mark_as_read')
-                        }
+                        className={`btn btn-link btn-animate ${
+                          this.state.my_vote == 1 ? 'text-info' : 'text-muted'
+                        }`}
+                        onClick={linkEvent(node, this.handleCommentUpvote)}
+                        data-tippy-content={i18n.t('upvote')}
                       >
-                        {this.state.readLoading ? (
-                          this.loadingIcon
-                        ) : (
-                          <svg
-                            class={`icon icon-inline ${node.comment.read &&
-                              'text-success'}`}
-                          >
-                            <use xlinkHref="#icon-check"></use>
-                          </svg>
+                        <svg class="icon icon-inline">
+                          <use xlinkHref="#icon-arrow-up"></use>
+                        </svg>
+                        {this.state.upvotes !== this.state.score && (
+                          <span class="ml-1">{this.state.upvotes}</span>
                         )}
                       </button>
-                    </li>
-                  )}
-                  {UserService.Instance.user && !this.props.viewOnly && (
-                    <>
-                      <li className="list-inline-item">
+                      {WebSocketService.Instance.site.enable_downvotes && (
                         <button
-                          className={`btn btn-link btn-sm btn-animate ${
-                            this.state.my_vote == 1 ? 'text-info' : 'text-muted'
+                          className={`btn btn-link btn-animate ${
+                            this.state.my_vote == -1
+                              ? 'text-danger'
+                              : 'text-muted'
                           }`}
-                          onClick={linkEvent(node, this.handleCommentUpvote)}
-                          data-tippy-content={i18n.t('upvote')}
+                          onClick={linkEvent(node, this.handleCommentDownvote)}
+                          data-tippy-content={i18n.t('downvote')}
                         >
                           <svg class="icon icon-inline">
-                            <use xlinkHref="#icon-arrow-up"></use>
+                            <use xlinkHref="#icon-arrow-down"></use>
                           </svg>
                           {this.state.upvotes !== this.state.score && (
-                            <span class="ml-1">{this.state.upvotes}</span>
+                            <span class="ml-1">{this.state.downvotes}</span>
                           )}
                         </button>
-                      </li>
-                      {WebSocketService.Instance.site.enable_downvotes && (
-                        <li className="list-inline-item">
-                          <button
-                            className={`btn btn-link btn-sm btn-animate ${
-                              this.state.my_vote == -1
-                                ? 'text-danger'
-                                : 'text-muted'
-                            }`}
-                            onClick={linkEvent(
-                              node,
-                              this.handleCommentDownvote
-                            )}
-                            data-tippy-content={i18n.t('downvote')}
-                          >
-                            <svg class="icon icon-inline">
-                              <use xlinkHref="#icon-arrow-down"></use>
-                            </svg>
-                            {this.state.upvotes !== this.state.score && (
-                              <span class="ml-1">{this.state.downvotes}</span>
-                            )}
-                          </button>
-                        </li>
                       )}
-                      <li className="list-inline-item">
-                        <button
-                          class="btn btn-link btn-sm btn-animate text-muted"
-                          onClick={linkEvent(this, this.handleSaveCommentClick)}
-                          data-tippy-content={
-                            node.comment.saved
-                              ? i18n.t('unsave')
-                              : i18n.t('save')
-                          }
-                        >
-                          {this.state.saveLoading ? (
-                            this.loadingIcon
-                          ) : (
-                            <svg
-                              class={`icon icon-inline ${node.comment.saved &&
-                                'text-warning'}`}
-                            >
-                              <use xlinkHref="#icon-star"></use>
-                            </svg>
-                          )}
-                        </button>
-                      </li>
-                      <li className="list-inline-item">
+                      <button
+                        class="btn btn-link btn-animate text-muted"
+                        onClick={linkEvent(this, this.handleSaveCommentClick)}
+                        data-tippy-content={
+                          node.comment.saved ? i18n.t('unsave') : i18n.t('save')
+                        }
+                      >
+                        {this.state.saveLoading ? (
+                          this.loadingIcon
+                        ) : (
+                          <svg
+                            class={`icon icon-inline ${node.comment.saved &&
+                              'text-warning'}`}
+                          >
+                            <use xlinkHref="#icon-star"></use>
+                          </svg>
+                        )}
+                      </button>
+                      <button
+                        class="btn btn-link btn-animate text-muted"
+                        onClick={linkEvent(this, this.handleReplyClick)}
+                        data-tippy-content={i18n.t('reply')}
+                      >
+                        <svg class="icon icon-inline">
+                          <use xlinkHref="#icon-reply1"></use>
+                        </svg>
+                      </button>
+                      {!this.state.showAdvanced ? (
                         <button
-                          class="btn btn-link btn-sm btn-animate text-muted"
-                          onClick={linkEvent(this, this.handleReplyClick)}
-                          data-tippy-content={i18n.t('reply')}
+                          className="btn btn-link btn-animate text-muted"
+                          onClick={linkEvent(this, this.handleShowAdvanced)}
+                          data-tippy-content={i18n.t('more')}
                         >
                           <svg class="icon icon-inline">
-                            <use xlinkHref="#icon-reply1"></use>
+                            <use xlinkHref="#icon-more-vertical"></use>
                           </svg>
                         </button>
-                      </li>
-                      {this.props.markable && this.linkBtn}
-                      {!this.state.showAdvanced ? (
-                        <li className="list-inline-item">
-                          <button
-                            className="btn btn-link btn-sm btn-animate text-muted"
-                            onClick={linkEvent(this, this.handleShowAdvanced)}
-                            data-tippy-content={i18n.t('more')}
-                          >
-                            <svg class="icon icon-inline">
-                              <use xlinkHref="#icon-more-vertical"></use>
-                            </svg>
-                          </button>
-                        </li>
                       ) : (
                         <>
                           {!this.myComment && (
-                            <li className="list-inline-item">
+                            <button class="btn btn-link btn-animate">
                               <Link
-                                class="btn btn-link btn-sm btn-animate text-muted"
+                                class="text-muted"
                                 to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
                                 title={i18n.t('message').toLowerCase()}
                               >
@@ -374,320 +348,292 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                                   <use xlinkHref="#icon-mail"></use>
                                 </svg>
                               </Link>
-                            </li>
+                            </button>
                           )}
-                          {!this.props.markable && this.linkBtn}
-                          <li className="list-inline-item">
-                            <button
-                              className="btn btn-link btn-sm btn-animate text-muted"
-                              onClick={linkEvent(this, this.handleViewSource)}
-                              data-tippy-content={i18n.t('view_source')}
+                          {!this.props.showContext && this.linkBtn}
+                          <button
+                            className="btn btn-link btn-animate text-muted"
+                            onClick={linkEvent(this, this.handleViewSource)}
+                            data-tippy-content={i18n.t('view_source')}
+                          >
+                            <svg
+                              class={`icon icon-inline ${this.state
+                                .viewSource && 'text-success'}`}
                             >
-                              <svg
-                                class={`icon icon-inline ${this.state
-                                  .viewSource && 'text-success'}`}
-                              >
-                                <use xlinkHref="#icon-file-text"></use>
-                              </svg>
-                            </button>
-                          </li>
+                              <use xlinkHref="#icon-file-text"></use>
+                            </svg>
+                          </button>
                           {this.myComment && (
                             <>
-                              <li className="list-inline-item">•</li>
-                              <li className="list-inline-item">
+                              <button
+                                class="btn btn-link btn-animate text-muted"
+                                onClick={linkEvent(this, this.handleEditClick)}
+                                data-tippy-content={i18n.t('edit')}
+                              >
+                                <svg class="icon icon-inline">
+                                  <use xlinkHref="#icon-edit"></use>
+                                </svg>
+                              </button>
+                              <button
+                                class="btn btn-link btn-animate text-muted"
+                                onClick={linkEvent(
+                                  this,
+                                  this.handleDeleteClick
+                                )}
+                                data-tippy-content={
+                                  !node.comment.deleted
+                                    ? i18n.t('delete')
+                                    : i18n.t('restore')
+                                }
+                              >
+                                <svg
+                                  class={`icon icon-inline ${node.comment
+                                    .deleted && 'text-danger'}`}
+                                >
+                                  <use xlinkHref="#icon-trash"></use>
+                                </svg>
+                              </button>
+                            </>
+                          )}
+                          {/* Admins and mods can remove comments */}
+                          {(this.canMod || this.canAdmin) && (
+                            <>
+                              {!node.comment.removed ? (
                                 <button
-                                  class="btn btn-link btn-sm btn-animate text-muted"
+                                  class="btn btn-link btn-animate text-muted"
                                   onClick={linkEvent(
                                     this,
-                                    this.handleEditClick
+                                    this.handleModRemoveShow
                                   )}
-                                  data-tippy-content={i18n.t('edit')}
                                 >
-                                  <svg class="icon icon-inline">
-                                    <use xlinkHref="#icon-edit"></use>
-                                  </svg>
+                                  {i18n.t('remove')}
                                 </button>
-                              </li>
-                              <li className="list-inline-item">
+                              ) : (
                                 <button
-                                  class="btn btn-link btn-sm btn-animate text-muted"
+                                  class="btn btn-link btn-animate text-muted"
                                   onClick={linkEvent(
                                     this,
-                                    this.handleDeleteClick
+                                    this.handleModRemoveSubmit
                                   )}
-                                  data-tippy-content={
-                                    !node.comment.deleted
-                                      ? i18n.t('delete')
-                                      : i18n.t('restore')
-                                  }
                                 >
-                                  <svg
-                                    class={`icon icon-inline ${node.comment
-                                      .deleted && 'text-danger'}`}
-                                  >
-                                    <use xlinkHref="#icon-trash"></use>
-                                  </svg>
+                                  {i18n.t('restore')}
                                 </button>
-                              </li>
+                              )}
                             </>
                           )}
-                          {/* Admins and mods can remove comments */}
-                          {(this.canMod || this.canAdmin) && (
+                          {/* Mods can ban from community, and appoint as mods to community */}
+                          {this.canMod && (
                             <>
-                              <li className="list-inline-item">
-                                {!node.comment.removed ? (
-                                  <span
-                                    class="pointer"
+                              {!this.isMod &&
+                                (!node.comment.banned_from_community ? (
+                                  <button
+                                    class="btn btn-link btn-animate text-muted"
                                     onClick={linkEvent(
                                       this,
-                                      this.handleModRemoveShow
+                                      this.handleModBanFromCommunityShow
                                     )}
                                   >
-                                    {i18n.t('remove')}
-                                  </span>
+                                    {i18n.t('ban')}
+                                  </button>
                                 ) : (
-                                  <span
-                                    class="pointer"
+                                  <button
+                                    class="btn btn-link btn-animate text-muted"
                                     onClick={linkEvent(
                                       this,
-                                      this.handleModRemoveSubmit
+                                      this.handleModBanFromCommunitySubmit
                                     )}
                                   >
-                                    {i18n.t('restore')}
-                                  </span>
-                                )}
-                              </li>
-                            </>
-                          )}
-                          {/* Mods can ban from community, and appoint as mods to community */}
-                          {this.canMod && (
-                            <>
-                              {!this.isMod && (
-                                <li className="list-inline-item">
-                                  {!node.comment.banned_from_community ? (
-                                    <span
-                                      class="pointer"
-                                      onClick={linkEvent(
-                                        this,
-                                        this.handleModBanFromCommunityShow
-                                      )}
-                                    >
-                                      {i18n.t('ban')}
-                                    </span>
-                                  ) : (
-                                    <span
-                                      class="pointer"
-                                      onClick={linkEvent(
-                                        this,
-                                        this.handleModBanFromCommunitySubmit
-                                      )}
-                                    >
-                                      {i18n.t('unban')}
-                                    </span>
-                                  )}
-                                </li>
-                              )}
-                              {!node.comment.banned_from_community && (
-                                <li className="list-inline-item">
-                                  {!this.state.showConfirmAppointAsMod ? (
-                                    <span
-                                      class="pointer"
-                                      onClick={linkEvent(
-                                        this,
-                                        this.handleShowConfirmAppointAsMod
-                                      )}
-                                    >
-                                      {this.isMod
-                                        ? i18n.t('remove_as_mod')
-                                        : i18n.t('appoint_as_mod')}
-                                    </span>
-                                  ) : (
-                                    <>
-                                      <span class="d-inline-block mr-1">
-                                        {i18n.t('are_you_sure')}
-                                      </span>
-                                      <span
-                                        class="pointer d-inline-block mr-1"
-                                        onClick={linkEvent(
-                                          this,
-                                          this.handleAddModToCommunity
-                                        )}
-                                      >
-                                        {i18n.t('yes')}
-                                      </span>
-                                      <span
-                                        class="pointer d-inline-block"
-                                        onClick={linkEvent(
-                                          this,
-                                          this.handleCancelConfirmAppointAsMod
-                                        )}
-                                      >
-                                        {i18n.t('no')}
-                                      </span>
-                                    </>
-                                  )}
-                                </li>
-                              )}
-                            </>
-                          )}
-                          {/* Community creators and admins can transfer community to another mod */}
-                          {(this.amCommunityCreator || this.canAdmin) &&
-                            this.isMod && (
-                              <li className="list-inline-item">
-                                {!this.state.showConfirmTransferCommunity ? (
-                                  <span
-                                    class="pointer"
+                                    {i18n.t('unban')}
+                                  </button>
+                                ))}
+                              {!node.comment.banned_from_community &&
+                                (!this.state.showConfirmAppointAsMod ? (
+                                  <button
+                                    class="btn btn-link btn-animate text-muted"
                                     onClick={linkEvent(
                                       this,
-                                      this.handleShowConfirmTransferCommunity
+                                      this.handleShowConfirmAppointAsMod
                                     )}
                                   >
-                                    {i18n.t('transfer_community')}
-                                  </span>
+                                    {this.isMod
+                                      ? i18n.t('remove_as_mod')
+                                      : i18n.t('appoint_as_mod')}
+                                  </button>
                                 ) : (
                                   <>
-                                    <span class="d-inline-block mr-1">
+                                    <button class="btn btn-link btn-animate text-muted">
                                       {i18n.t('are_you_sure')}
-                                    </span>
-                                    <span
-                                      class="pointer d-inline-block mr-1"
+                                    </button>
+                                    <button
+                                      class="btn btn-link btn-animate text-muted"
                                       onClick={linkEvent(
                                         this,
-                                        this.handleTransferCommunity
+                                        this.handleAddModToCommunity
                                       )}
                                     >
                                       {i18n.t('yes')}
-                                    </span>
-                                    <span
-                                      class="pointer d-inline-block"
+                                    </button>
+                                    <button
+                                      class="btn btn-link btn-animate text-muted"
                                       onClick={linkEvent(
                                         this,
-                                        this
-                                          .handleCancelShowConfirmTransferCommunity
+                                        this.handleCancelConfirmAppointAsMod
                                       )}
                                     >
                                       {i18n.t('no')}
-                                    </span>
+                                    </button>
                                   </>
+                                ))}
+                            </>
+                          )}
+                          {/* Community creators and admins can transfer community to another mod */}
+                          {(this.amCommunityCreator || this.canAdmin) &&
+                            this.isMod &&
+                            (!this.state.showConfirmTransferCommunity ? (
+                              <button
+                                class="btn btn-link btn-animate text-muted"
+                                onClick={linkEvent(
+                                  this,
+                                  this.handleShowConfirmTransferCommunity
                                 )}
-                              </li>
-                            )}
+                              >
+                                {i18n.t('transfer_community')}
+                              </button>
+                            ) : (
+                              <>
+                                <button class="btn btn-link btn-animate text-muted">
+                                  {i18n.t('are_you_sure')}
+                                </button>
+                                <button
+                                  class="btn btn-link btn-animate text-muted"
+                                  onClick={linkEvent(
+                                    this,
+                                    this.handleTransferCommunity
+                                  )}
+                                >
+                                  {i18n.t('yes')}
+                                </button>
+                                <button
+                                  class="btn btn-link btn-animate text-muted"
+                                  onClick={linkEvent(
+                                    this,
+                                    this
+                                      .handleCancelShowConfirmTransferCommunity
+                                  )}
+                                >
+                                  {i18n.t('no')}
+                                </button>
+                              </>
+                            ))}
                           {/* Admins can ban from all, and appoint other admins */}
                           {this.canAdmin && (
                             <>
-                              {!this.isAdmin && (
-                                <li className="list-inline-item">
-                                  {!node.comment.banned ? (
-                                    <span
-                                      class="pointer"
-                                      onClick={linkEvent(
-                                        this,
-                                        this.handleModBanShow
-                                      )}
-                                    >
-                                      {i18n.t('ban_from_site')}
-                                    </span>
-                                  ) : (
-                                    <span
-                                      class="pointer"
+                              {!this.isAdmin &&
+                                (!node.comment.banned ? (
+                                  <button
+                                    class="btn btn-link btn-animate text-muted"
+                                    onClick={linkEvent(
+                                      this,
+                                      this.handleModBanShow
+                                    )}
+                                  >
+                                    {i18n.t('ban_from_site')}
+                                  </button>
+                                ) : (
+                                  <button
+                                    class="btn btn-link btn-animate text-muted"
+                                    onClick={linkEvent(
+                                      this,
+                                      this.handleModBanSubmit
+                                    )}
+                                  >
+                                    {i18n.t('unban_from_site')}
+                                  </button>
+                                ))}
+                              {!node.comment.banned &&
+                                (!this.state.showConfirmAppointAsAdmin ? (
+                                  <button
+                                    class="btn btn-link btn-animate text-muted"
+                                    onClick={linkEvent(
+                                      this,
+                                      this.handleShowConfirmAppointAsAdmin
+                                    )}
+                                  >
+                                    {this.isAdmin
+                                      ? i18n.t('remove_as_admin')
+                                      : i18n.t('appoint_as_admin')}
+                                  </button>
+                                ) : (
+                                  <>
+                                    <button class="btn btn-link btn-animate text-muted">
+                                      {i18n.t('are_you_sure')}
+                                    </button>
+                                    <button
+                                      class="btn btn-link btn-animate text-muted"
                                       onClick={linkEvent(
                                         this,
-                                        this.handleModBanSubmit
+                                        this.handleAddAdmin
                                       )}
                                     >
-                                      {i18n.t('unban_from_site')}
-                                    </span>
-                                  )}
-                                </li>
-                              )}
-                              {!node.comment.banned && (
-                                <li className="list-inline-item">
-                                  {!this.state.showConfirmAppointAsAdmin ? (
-                                    <span
-                                      class="pointer"
+                                      {i18n.t('yes')}
+                                    </button>
+                                    <button
+                                      class="btn btn-link btn-animate text-muted"
                                       onClick={linkEvent(
                                         this,
-                                        this.handleShowConfirmAppointAsAdmin
+                                        this.handleCancelConfirmAppointAsAdmin
                                       )}
                                     >
-                                      {this.isAdmin
-                                        ? i18n.t('remove_as_admin')
-                                        : i18n.t('appoint_as_admin')}
-                                    </span>
-                                  ) : (
-                                    <>
-                                      <span class="d-inline-block mr-1">
-                                        {i18n.t('are_you_sure')}
-                                      </span>
-                                      <span
-                                        class="pointer d-inline-block mr-1"
-                                        onClick={linkEvent(
-                                          this,
-                                          this.handleAddAdmin
-                                        )}
-                                      >
-                                        {i18n.t('yes')}
-                                      </span>
-                                      <span
-                                        class="pointer d-inline-block"
-                                        onClick={linkEvent(
-                                          this,
-                                          this.handleCancelConfirmAppointAsAdmin
-                                        )}
-                                      >
-                                        {i18n.t('no')}
-                                      </span>
-                                    </>
-                                  )}
-                                </li>
-                              )}
+                                      {i18n.t('no')}
+                                    </button>
+                                  </>
+                                ))}
                             </>
                           )}
                           {/* Site Creator can transfer to another admin */}
-                          {this.amSiteCreator && this.isAdmin && (
-                            <li className="list-inline-item">
-                              {!this.state.showConfirmTransferSite ? (
-                                <span
-                                  class="pointer"
+                          {this.amSiteCreator &&
+                            this.isAdmin &&
+                            (!this.state.showConfirmTransferSite ? (
+                              <button
+                                class="btn btn-link btn-animate text-muted"
+                                onClick={linkEvent(
+                                  this,
+                                  this.handleShowConfirmTransferSite
+                                )}
+                              >
+                                {i18n.t('transfer_site')}
+                              </button>
+                            ) : (
+                              <>
+                                <button class="btn btn-link btn-animate text-muted">
+                                  {i18n.t('are_you_sure')}
+                                </button>
+                                <button
+                                  class="btn btn-link btn-animate text-muted"
                                   onClick={linkEvent(
                                     this,
-                                    this.handleShowConfirmTransferSite
+                                    this.handleTransferSite
                                   )}
                                 >
-                                  {i18n.t('transfer_site')}
-                                </span>
-                              ) : (
-                                <>
-                                  <span class="d-inline-block mr-1">
-                                    {i18n.t('are_you_sure')}
-                                  </span>
-                                  <span
-                                    class="pointer d-inline-block mr-1"
-                                    onClick={linkEvent(
-                                      this,
-                                      this.handleTransferSite
-                                    )}
-                                  >
-                                    {i18n.t('yes')}
-                                  </span>
-                                  <span
-                                    class="pointer d-inline-block"
-                                    onClick={linkEvent(
-                                      this,
-                                      this.handleCancelShowConfirmTransferSite
-                                    )}
-                                  >
-                                    {i18n.t('no')}
-                                  </span>
-                                </>
-                              )}
-                            </li>
-                          )}
+                                  {i18n.t('yes')}
+                                </button>
+                                <button
+                                  class="btn btn-link btn-animate text-muted"
+                                  onClick={linkEvent(
+                                    this,
+                                    this.handleCancelShowConfirmTransferSite
+                                  )}
+                                >
+                                  {i18n.t('no')}
+                                </button>
+                              </>
+                            ))}
                         </>
                       )}
                     </>
                   )}
-                </ul>
+                </div>
+                {/* end of button group */}
               </div>
             )}
           </div>
@@ -761,17 +707,19 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
   get linkBtn() {
     let node = this.props.node;
     return (
-      <li className="list-inline-item">
+      <button className="btn btn-link btn-animate">
         <Link
-          className="btn btn-link btn-sm btn-animate text-muted"
+          class="text-muted"
           to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
-          title={i18n.t('link')}
+          title={
+            this.props.showContext ? i18n.t('show_context') : i18n.t('link')
+          }
         >
           <svg class="icon icon-inline">
             <use xlinkHref="#icon-link"></use>
           </svg>
         </Link>
-      </li>
+      </button>
     );
   }
 
index ebbef8fad48b5d3dab4ea3732c90905d08a68c81..875db1f2a845ae642e43b6104899d6ba8cbfebed 100644 (file)
@@ -20,6 +20,7 @@ interface CommentNodesProps {
   viewOnly?: boolean;
   locked?: boolean;
   markable?: boolean;
+  showContext?: boolean;
   showCommunity?: boolean;
   sort?: CommentSortType;
   sortType?: SortType;
@@ -47,6 +48,7 @@ export class CommentNodes extends Component<
             admins={this.props.admins}
             postCreatorId={this.props.postCreatorId}
             markable={this.props.markable}
+            showContext={this.props.showContext}
             showCommunity={this.props.showCommunity}
             sort={this.props.sort}
             sortType={this.props.sortType}
index 4e8e9d1b63279a13ce5da0ae0b40b051c946ff0d..a921de2c130fb258fb3ac7029cdb8341aad08652 100644 (file)
@@ -189,6 +189,7 @@ export class Community extends Component<any, State> {
         nodes={commentsToFlatNodes(this.state.comments)}
         noIndent
         sortType={this.state.sort}
+        showContext
       />
     );
   }
index afd9bf95f439fb19ce361b171b2c749d61520ee1..4fa1498ac4eede6f685cbad554fbf295120c1cd6 100644 (file)
@@ -1,5 +1,4 @@
 import { Component, linkEvent } from 'inferno';
-import { Link } from 'inferno-router';
 import { Subscription } from 'rxjs';
 import { retryWhen, delay, take } from 'rxjs/operators';
 import {
@@ -34,14 +33,13 @@ import { CommentNodes } from './comment-nodes';
 import { PrivateMessage } from './private-message';
 import { SortSelect } from './sort-select';
 import { i18n } from '../i18next';
-import { T } from 'inferno-i18next';
 
 enum UnreadOrAll {
   Unread,
   All,
 }
 
-enum UnreadType {
+enum MessageType {
   All,
   Replies,
   Mentions,
@@ -52,7 +50,7 @@ type ReplyType = Comment | PrivateMessageI;
 
 interface InboxState {
   unreadOrAll: UnreadOrAll;
-  unreadType: UnreadType;
+  messageType: MessageType;
   replies: Array<Comment>;
   mentions: Array<Comment>;
   messages: Array<PrivateMessageI>;
@@ -64,7 +62,7 @@ export class Inbox extends Component<any, InboxState> {
   private subscription: Subscription;
   private emptyState: InboxState = {
     unreadOrAll: UnreadOrAll.Unread,
-    unreadType: UnreadType.All,
+    messageType: MessageType.All,
     replies: [],
     mentions: [],
     messages: [],
@@ -100,26 +98,19 @@ export class Inbox extends Component<any, InboxState> {
   }
 
   render() {
-    let user = UserService.Instance.user;
     return (
       <div class="container">
         <div class="row">
           <div class="col-12">
-            <h5 class="mb-0">
-              <T
-                class="d-inline"
-                i18nKey="inbox_for"
-                interpolation={{ user: user.username }}
-              >
-                #<Link to={`/u/${user.username}`}>#</Link>
-              </T>
+            <h5 class="mb-1">
+              {i18n.t('inbox')}
               <small>
                 <a
                   href={`/feeds/inbox/${UserService.Instance.auth}.xml`}
                   target="_blank"
                   title="RSS"
                 >
-                  <svg class="icon mx-2 text-muted small">
+                  <svg class="icon ml-2 text-muted small">
                     <use xlinkHref="#icon-rss">#</use>
                   </svg>
                 </a>
@@ -139,10 +130,10 @@ export class Inbox extends Component<any, InboxState> {
                 </ul>
               )}
             {this.selects()}
-            {this.state.unreadType == UnreadType.All && this.all()}
-            {this.state.unreadType == UnreadType.Replies && this.replies()}
-            {this.state.unreadType == UnreadType.Mentions && this.mentions()}
-            {this.state.unreadType == UnreadType.Messages && this.messages()}
+            {this.state.messageType == MessageType.All && this.all()}
+            {this.state.messageType == MessageType.Replies && this.replies()}
+            {this.state.messageType == MessageType.Mentions && this.mentions()}
+            {this.state.messageType == MessageType.Messages && this.messages()}
             {this.paginator()}
           </div>
         </div>
@@ -150,29 +141,103 @@ export class Inbox extends Component<any, InboxState> {
     );
   }
 
-  selects() {
+  unreadOrAllRadios() {
     return (
-      <div className="mb-2">
-        <select
-          value={this.state.unreadOrAll}
-          onChange={linkEvent(this, this.handleUnreadOrAllChange)}
-          class="custom-select custom-select-sm w-auto mr-2"
+      <div class="btn-group btn-group-toggle">
+        <label
+          className={`btn btn-sm btn-secondary pointer
+            ${this.state.unreadOrAll == UnreadOrAll.Unread && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={UnreadOrAll.Unread}
+            checked={this.state.unreadOrAll == UnreadOrAll.Unread}
+            onChange={linkEvent(this, this.handleUnreadOrAllChange)}
+          />
+          {i18n.t('unread')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer
+            ${this.state.unreadOrAll == UnreadOrAll.All && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={UnreadOrAll.All}
+            checked={this.state.unreadOrAll == UnreadOrAll.All}
+            onChange={linkEvent(this, this.handleUnreadOrAllChange)}
+          />
+          {i18n.t('all')}
+        </label>
+      </div>
+    );
+  }
+
+  messageTypeRadios() {
+    return (
+      <div class="btn-group btn-group-toggle">
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.messageType == MessageType.All && 'active'}
+          `}
         >
-          <option disabled>{i18n.t('type')}</option>
-          <option value={UnreadOrAll.Unread}>{i18n.t('unread')}</option>
-          <option value={UnreadOrAll.All}>{i18n.t('all')}</option>
-        </select>
-        <select
-          value={this.state.unreadType}
-          onChange={linkEvent(this, this.handleUnreadTypeChange)}
-          class="custom-select custom-select-sm w-auto mr-2"
+          <input
+            type="radio"
+            value={MessageType.All}
+            checked={this.state.messageType == MessageType.All}
+            onChange={linkEvent(this, this.handleMessageTypeChange)}
+          />
+          {i18n.t('all')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.messageType == MessageType.Replies && 'active'}
+          `}
         >
-          <option disabled>{i18n.t('type')}</option>
-          <option value={UnreadType.All}>{i18n.t('all')}</option>
-          <option value={UnreadType.Replies}>{i18n.t('replies')}</option>
-          <option value={UnreadType.Mentions}>{i18n.t('mentions')}</option>
-          <option value={UnreadType.Messages}>{i18n.t('messages')}</option>
-        </select>
+          <input
+            type="radio"
+            value={MessageType.Replies}
+            checked={this.state.messageType == MessageType.Replies}
+            onChange={linkEvent(this, this.handleMessageTypeChange)}
+          />
+          {i18n.t('replies')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.messageType == MessageType.Mentions && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={MessageType.Mentions}
+            checked={this.state.messageType == MessageType.Mentions}
+            onChange={linkEvent(this, this.handleMessageTypeChange)}
+          />
+          {i18n.t('mentions')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.messageType == MessageType.Messages && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={MessageType.Messages}
+            checked={this.state.messageType == MessageType.Messages}
+            onChange={linkEvent(this, this.handleMessageTypeChange)}
+          />
+          {i18n.t('messages')}
+        </label>
+      </div>
+    );
+  }
+
+  selects() {
+    return (
+      <div className="mb-2">
+        <span class="mr-3">{this.unreadOrAllRadios()}</span>
+        <span class="mr-3">{this.messageTypeRadios()}</span>
         <SortSelect
           sort={this.state.sort}
           onChange={this.handleSortChange}
@@ -196,7 +261,12 @@ export class Inbox extends Component<any, InboxState> {
       <div>
         {combined.map(i =>
           isCommentType(i) ? (
-            <CommentNodes nodes={[{ comment: i }]} noIndent markable />
+            <CommentNodes
+              nodes={[{ comment: i }]}
+              noIndent
+              markable
+              showContext
+            />
           ) : (
             <PrivateMessage privateMessage={i} />
           )
@@ -212,6 +282,7 @@ export class Inbox extends Component<any, InboxState> {
           nodes={commentsToFlatNodes(this.state.replies)}
           noIndent
           markable
+          showContext
         />
       </div>
     );
@@ -221,7 +292,12 @@ export class Inbox extends Component<any, InboxState> {
     return (
       <div>
         {this.state.mentions.map(mention => (
-          <CommentNodes nodes={[{ comment: mention }]} noIndent markable />
+          <CommentNodes
+            nodes={[{ comment: mention }]}
+            noIndent
+            markable
+            showContext
+          />
         ))}
       </div>
     );
@@ -277,8 +353,8 @@ export class Inbox extends Component<any, InboxState> {
     i.refetch();
   }
 
-  handleUnreadTypeChange(i: Inbox, event: any) {
-    i.state.unreadType = Number(event.target.value);
+  handleMessageTypeChange(i: Inbox, event: any) {
+    i.state.messageType = Number(event.target.value);
     i.state.page = 1;
     i.setState(i.state);
     i.refetch();
index 51b31ced9dcd7e6aa23353125967dd61154dee41..38003312768168618cf7c4cf10ecd0a3c2fa9ed0 100644 (file)
@@ -423,6 +423,7 @@ export class Main extends Component<any, MainState> {
         noIndent
         showCommunity
         sortType={this.state.sort}
+        showContext
       />
     );
   }
index ef3f84309e71a05bfa1dcb77ea9681a866ba01fc..ea9b619b80b69f1060b998a8da5b56f01efb16b1 100644 (file)
@@ -26,6 +26,8 @@ import {
   fetchLimit,
   isCommentType,
   toast,
+  messageToastify,
+  md,
 } from '../utils';
 import { version } from '../version';
 import { i18n } from '../i18next';
@@ -100,6 +102,22 @@ export class Navbar extends Component<any, NavbarState> {
         <Link title={version} class="navbar-brand" to="/">
           {this.state.siteName}
         </Link>
+        {this.state.isLoggedIn && (
+          <Link
+            class="ml-auto p-0 navbar-toggler nav-link"
+            to="/inbox"
+            title={i18n.t('inbox')}
+          >
+            <svg class="icon">
+              <use xlinkHref="#icon-bell"></use>
+            </svg>
+            {this.state.unreadCount > 0 && (
+              <span class="ml-1 badge badge-light">
+                {this.state.unreadCount}
+              </span>
+            )}
+          </Link>
+        )}
         <button
           class="navbar-toggler"
           type="button"
@@ -350,21 +368,33 @@ export class Navbar extends Component<any, NavbarState> {
   }
 
   notify(reply: Comment | PrivateMessage) {
+    let creator_name = reply.creator_name;
+    let creator_avatar = reply.creator_avatar
+      ? reply.creator_avatar
+      : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`;
+    let link = isCommentType(reply)
+      ? `/post/${reply.post_id}/comment/${reply.id}`
+      : `/inbox`;
+    let body = md.render(reply.content);
+
+    messageToastify(
+      creator_name,
+      creator_avatar,
+      body,
+      link,
+      this.context.router
+    );
+
     if (Notification.permission !== 'granted') Notification.requestPermission();
     else {
       var notification = new Notification(reply.creator_name, {
-        icon: reply.creator_avatar
-          ? reply.creator_avatar
-          : `${window.location.protocol}//${window.location.host}/static/assets/apple-touch-icon.png`,
-        body: `${reply.content}`,
+        icon: creator_avatar,
+        body: body,
       });
 
       notification.onclick = () => {
-        this.context.router.history.push(
-          isCommentType(reply)
-            ? `/post/${reply.post_id}/comment/${reply.id}`
-            : `/inbox`
-        );
+        event.preventDefault();
+        this.context.router.history.push(link);
       };
     }
   }
index 628ec02757769d6c0f5dfd36bb3ad0bbdcb5b26b..ff863dcb0d98f154f48f7b9fb80baa55fc61775a 100644 (file)
@@ -257,7 +257,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
             data-tippy-content={i18n.t('upvote')}
           >
             <svg class="icon upvote">
-              <use xlinkHref="#icon-arrow-up"></use>
+              <use xlinkHref="#icon-arrow-up1"></use>
             </svg>
           </button>
           <div
@@ -275,7 +275,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
               data-tippy-content={i18n.t('downvote')}
             >
               <svg class="icon downvote">
-                <use xlinkHref="#icon-arrow-down"></use>
+                <use xlinkHref="#icon-arrow-down1"></use>
               </svg>
             </button>
           )}
@@ -524,7 +524,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                       <>
                         <li className="list-inline-item">
                           <button
-                            class="btn btn-link btn-animate btn-sm text-muted"
+                            class="btn btn-sm btn-link btn-animate text-muted"
                             onClick={linkEvent(this, this.handleSavePostClick)}
                             data-tippy-content={
                               post.saved ? i18n.t('unsave') : i18n.t('save')
@@ -540,7 +540,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                         </li>
                         <li className="list-inline-item">
                           <Link
-                            class="btn btn-link btn-animate btn-sm text-muted"
+                            class="btn btn-sm btn-link btn-animate text-muted"
                             to={`/create_post${this.crossPostParams}`}
                             title={i18n.t('cross_post')}
                           >
@@ -555,7 +555,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                       <>
                         <li className="list-inline-item">
                           <button
-                            class="btn btn-link btn-animate btn-sm text-muted"
+                            class="btn btn-sm btn-link btn-animate text-muted"
                             onClick={linkEvent(this, this.handleEditClick)}
                             data-tippy-content={i18n.t('edit')}
                           >
@@ -566,7 +566,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                         </li>
                         <li className="list-inline-item">
                           <button
-                            class="btn btn-link btn-animate btn-sm text-muted"
+                            class="btn btn-sm btn-link btn-animate text-muted"
                             onClick={linkEvent(this, this.handleDeleteClick)}
                             data-tippy-content={
                               !post.deleted
@@ -588,7 +588,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                     {!this.state.showAdvanced && this.props.showBody ? (
                       <li className="list-inline-item">
                         <button
-                          class="btn btn-link btn-animate btn-sm text-muted"
+                          class="btn btn-sm btn-link btn-animate text-muted"
                           onClick={linkEvent(this, this.handleShowAdvanced)}
                           data-tippy-content={i18n.t('more')}
                         >
@@ -602,7 +602,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                         {this.props.showBody && post.body && (
                           <li className="list-inline-item">
                             <button
-                              class="btn btn-link btn-animate btn-sm text-muted"
+                              class="btn btn-sm btn-link btn-animate text-muted"
                               onClick={linkEvent(this, this.handleViewSource)}
                               data-tippy-content={i18n.t('view_source')}
                             >
@@ -619,7 +619,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                           <>
                             <li className="list-inline-item">
                               <button
-                                class="btn btn-link btn-animate btn-sm text-muted"
+                                class="btn btn-sm btn-link btn-animate text-muted"
                                 onClick={linkEvent(this, this.handleModLock)}
                                 data-tippy-content={
                                   post.locked
@@ -637,7 +637,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                             </li>
                             <li className="list-inline-item">
                               <button
-                                class="btn btn-link btn-animate btn-sm text-muted"
+                                class="btn btn-sm btn-link btn-animate text-muted"
                                 onClick={linkEvent(this, this.handleModSticky)}
                                 data-tippy-content={
                                   post.stickied
index 518f07c2567d5877fd4b2abe6ac303b61c5d13ea..f51ba6ff91c62b6ef24578c22ded673360b28637 100644 (file)
@@ -276,6 +276,7 @@ export class Post extends Component<any, PostState> {
             moderators={this.state.moderators}
             admins={this.state.admins}
             postCreatorId={this.state.post.creator_id}
+            showContext
           />
         </div>
       </div>
index e77602690eee0a3cb6c8f2999c247d41d76aef98..16deec3eb7d4b2611884d6f251de87b94858c84f 100644 (file)
@@ -88,15 +88,21 @@ export class Symbols extends Component<any, any> {
             <path d="M17 19h-12c-0.553 0-1-0.447-1-1s0.447-1 1-1h12c0.553 0 1 0.447 1 1s-0.447 1-1 1z"></path>
             <path d="M17.5 5h-12.5v9c0 1.1 0.9 2 2 2h8c1.1 0 2-0.9 2-2v-2h0.5c1.93 0 3.5-1.57 3.5-3.5s-1.57-3.5-3.5-3.5zM15 14h-8v-7h8v7zM17.5 10h-1.5v-3h1.5c0.827 0 1.5 0.673 1.5 1.5s-0.673 1.5-1.5 1.5z"></path>
           </symbol>
-          <symbol id="icon-rss" viewBox="0 0 32 32">
-            <path d="M4.259 23.467c-2.35 0-4.259 1.917-4.259 4.252 0 2.349 1.909 4.244 4.259 4.244 2.358 0 4.265-1.895 4.265-4.244-0-2.336-1.907-4.252-4.265-4.252zM0.005 10.873v6.133c3.993 0 7.749 1.562 10.577 4.391 2.825 2.822 4.384 6.595 4.384 10.603h6.16c-0-11.651-9.478-21.127-21.121-21.127zM0.012 0v6.136c14.243 0 25.836 11.604 25.836 25.864h6.152c0-17.64-14.352-32-31.988-32z"></path>
+          <symbol id="icon-rss" viewBox="0 0 24 24">
+            <path d="M4 12c2.209 0 4.208 0.894 5.657 2.343s2.343 3.448 2.343 5.657c0 0.552 0.448 1 1 1s1-0.448 1-1c0-2.761-1.12-5.263-2.929-7.071s-4.31-2.929-7.071-2.929c-0.552 0-1 0.448-1 1s0.448 1 1 1zM4 5c4.142 0 7.891 1.678 10.607 4.393s4.393 6.465 4.393 10.607c0 0.552 0.448 1 1 1s1-0.448 1-1c0-4.694-1.904-8.946-4.979-12.021s-7.327-4.979-12.021-4.979c-0.552 0-1 0.448-1 1s0.448 1 1 1zM7 19c0-0.552-0.225-1.053-0.586-1.414s-0.862-0.586-1.414-0.586-1.053 0.225-1.414 0.586-0.586 0.862-0.586 1.414 0.225 1.053 0.586 1.414 0.862 0.586 1.414 0.586 1.053-0.225 1.414-0.586 0.586-0.862 0.586-1.414z"></path>
           </symbol>
-          <symbol id="icon-arrow-down" viewBox="0 0 26 28">
-            <path d="M25.172 13c0 0.531-0.219 1.047-0.578 1.406l-10.172 10.187c-0.375 0.359-0.891 0.578-1.422 0.578s-1.047-0.219-1.406-0.578l-10.172-10.187c-0.375-0.359-0.594-0.875-0.594-1.406s0.219-1.047 0.594-1.422l1.156-1.172c0.375-0.359 0.891-0.578 1.422-0.578s1.047 0.219 1.406 0.578l4.594 4.594v-11c0-1.094 0.906-2 2-2h2c1.094 0 2 0.906 2 2v11l4.594-4.594c0.359-0.359 0.875-0.578 1.406-0.578s1.047 0.219 1.422 0.578l1.172 1.172c0.359 0.375 0.578 0.891 0.578 1.422z"></path>
+          <symbol id="icon-arrow-down" viewBox="0 0 24 24">
+            <path d="M18.293 11.293l-5.293 5.293v-11.586c0-0.552-0.448-1-1-1s-1 0.448-1 1v11.586l-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414l7 7c0.092 0.092 0.202 0.166 0.324 0.217 0.245 0.101 0.521 0.101 0.766 0 0.118-0.049 0.228-0.121 0.324-0.217l7-7c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0z"></path>
+          </symbol>
+          <symbol id="icon-arrow-up" viewBox="0 0 24 24">
+            <path d="M5.707 12.707l5.293-5.293v11.586c0 0.552 0.448 1 1 1s1-0.448 1-1v-11.586l5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-7-7c-0.092-0.092-0.202-0.166-0.324-0.217s-0.253-0.076-0.383-0.076c-0.256 0-0.512 0.098-0.707 0.293l-7 7c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0z"></path>
           </symbol>
-          <symbol id="icon-arrow-up" viewBox="0 0 26 28">
+          <symbol id="icon-arrow-up1" viewBox="0 0 26 28">
             <path d="M25.172 15.172c0 0.531-0.219 1.031-0.578 1.406l-1.172 1.172c-0.375 0.375-0.891 0.594-1.422 0.594s-1.047-0.219-1.406-0.594l-4.594-4.578v11c0 1.125-0.938 1.828-2 1.828h-2c-1.062 0-2-0.703-2-1.828v-11l-4.594 4.578c-0.359 0.375-0.875 0.594-1.406 0.594s-1.047-0.219-1.406-0.594l-1.172-1.172c-0.375-0.375-0.594-0.875-0.594-1.406s0.219-1.047 0.594-1.422l10.172-10.172c0.359-0.375 0.875-0.578 1.406-0.578s1.047 0.203 1.422 0.578l10.172 10.172c0.359 0.375 0.578 0.891 0.578 1.422z"></path>
           </symbol>
+          <symbol id="icon-arrow-down1" viewBox="0 0 26 28">
+            <path d="M25.172 13c0 0.531-0.219 1.047-0.578 1.406l-10.172 10.187c-0.375 0.359-0.891 0.578-1.422 0.578s-1.047-0.219-1.406-0.578l-10.172-10.187c-0.375-0.359-0.594-0.875-0.594-1.406s0.219-1.047 0.594-1.422l1.156-1.172c0.375-0.359 0.891-0.578 1.422-0.578s1.047 0.219 1.406 0.578l4.594 4.594v-11c0-1.094 0.906-2 2-2h2c1.094 0 2 0.906 2 2v11l4.594-4.594c0.359-0.359 0.875-0.578 1.406-0.578s1.047 0.219 1.422 0.578l1.172 1.172c0.359 0.375 0.578 0.891 0.578 1.422z"></path>
+          </symbol>
           <symbol id="icon-mail" viewBox="0 0 24 24">
             <path d="M3 7.921l8.427 5.899c0.34 0.235 0.795 0.246 1.147 0l8.426-5.899v10.079c0 0.272-0.11 0.521-0.295 0.705s-0.433 0.295-0.705 0.295h-16c-0.272 0-0.521-0.11-0.705-0.295s-0.295-0.433-0.295-0.705zM1 5.983c0 0.010 0 0.020 0 0.030v11.987c0 0.828 0.34 1.579 0.88 2.12s1.292 0.88 2.12 0.88h16c0.828 0 1.579-0.34 2.12-0.88s0.88-1.292 0.88-2.12v-11.988c0-0.010 0-0.020 0-0.030-0.005-0.821-0.343-1.565-0.88-2.102-0.541-0.54-1.292-0.88-2.12-0.88h-16c-0.828 0-1.579 0.34-2.12 0.88-0.537 0.537-0.875 1.281-0.88 2.103zM20.894 5.554l-8.894 6.225-8.894-6.225c0.048-0.096 0.112-0.183 0.188-0.259 0.185-0.185 0.434-0.295 0.706-0.295h16c0.272 0 0.521 0.11 0.705 0.295 0.076 0.076 0.14 0.164 0.188 0.259z"></path>
           </symbol>
index 0b0d11352e2f2cf25e88627b1cce02c2bc1cc322..bf67a5fdc204fcef9b35af82443abdd59719be8d 100644 (file)
@@ -242,27 +242,74 @@ export class User extends Component<any, UserState> {
     );
   }
 
-  selects() {
+  viewRadios() {
     return (
-      <div className="mb-2">
-        <select
-          value={this.state.view}
-          onChange={linkEvent(this, this.handleViewChange)}
-          class="custom-select custom-select-sm w-auto"
+      <div class="btn-group btn-group-toggle">
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.view == View.Overview && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={View.Overview}
+            checked={this.state.view == View.Overview}
+            onChange={linkEvent(this, this.handleViewChange)}
+          />
+          {i18n.t('overview')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.view == View.Comments && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={View.Comments}
+            checked={this.state.view == View.Comments}
+            onChange={linkEvent(this, this.handleViewChange)}
+          />
+          {i18n.t('comments')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.view == View.Posts && 'active'}
+          `}
+        >
+          <input
+            type="radio"
+            value={View.Posts}
+            checked={this.state.view == View.Posts}
+            onChange={linkEvent(this, this.handleViewChange)}
+          />
+          {i18n.t('posts')}
+        </label>
+        <label
+          className={`btn btn-sm btn-secondary pointer btn-outline-light
+            ${this.state.view == View.Saved && 'active'}
+          `}
         >
-          <option disabled>{i18n.t('view')}</option>
-          <option value={View.Overview}>{i18n.t('overview')}</option>
-          <option value={View.Comments}>{i18n.t('comments')}</option>
-          <option value={View.Posts}>{i18n.t('posts')}</option>
-          <option value={View.Saved}>{i18n.t('saved')}</option>
-        </select>
-        <span class="ml-2">
-          <SortSelect
-            sort={this.state.sort}
-            onChange={this.handleSortChange}
-            hideHot
+          <input
+            type="radio"
+            value={View.Saved}
+            checked={this.state.view == View.Saved}
+            onChange={linkEvent(this, this.handleViewChange)}
           />
-        </span>
+          {i18n.t('saved')}
+        </label>
+      </div>
+    );
+  }
+
+  selects() {
+    return (
+      <div className="mb-2">
+        <span class="mr-3">{this.viewRadios()}</span>
+        <SortSelect
+          sort={this.state.sort}
+          onChange={this.handleSortChange}
+          hideHot
+        />
         <a
           href={`/feeds/u/${this.state.username}.xml?sort=${
             SortType[this.state.sort]
@@ -312,6 +359,7 @@ export class User extends Component<any, UserState> {
                 nodes={[{ comment: i.data as Comment }]}
                 admins={this.state.admins}
                 noIndent
+                showContext
               />
             )}
           </div>
@@ -327,6 +375,7 @@ export class User extends Component<any, UserState> {
           nodes={commentsToFlatNodes(this.state.comments)}
           admins={this.state.admins}
           noIndent
+          showContext
         />
       </div>
     );
index 89fbe51c881344c9b5760815beca1baf366bfdef..48bd175e25a5d2933ef5db27804df5b61a161ee9 100644 (file)
@@ -218,7 +218,7 @@ export function validURL(str: string) {
 }
 
 export function validEmail(email: string) {
-  let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+  let re = /^(([^\s"(),.:;<>@[\\\]]+(\.[^\s"(),.:;<>@[\\\]]+)*)|(".+"))@((\[(?:\d{1,3}\.){3}\d{1,3}])|(([\dA-Za-z\-]+\.)+[A-Za-z]{2,}))$/;
   return re.test(String(email).toLowerCase());
 }
 
@@ -436,6 +436,32 @@ export function toast(text: string, background: string = 'success') {
   }).showToast();
 }
 
+export function messageToastify(
+  creator: string,
+  avatar: string,
+  body: string,
+  link: string,
+  router: any
+) {
+  let backgroundColor = `var(--light)`;
+
+  let toast = Toastify({
+    text: `${body}<br />${creator}`,
+    avatar: avatar,
+    backgroundColor: backgroundColor,
+    close: true,
+    gravity: 'top',
+    position: 'right',
+    duration: 0,
+    onClick: () => {
+      if (toast) {
+        toast.hideToast();
+        router.history.push(link);
+      }
+    },
+  }).showToast();
+}
+
 export function setupTribute(): Tribute {
   return new Tribute({
     collection: [
index 531b5a8e474178f633c96eb47965722e107dce4d..fdf2c3470463be11a1184b236d4b7f0f6016cb34 100644 (file)
@@ -1 +1 @@
-export const version: string = 'v0.6.41';
+export const version: string = 'v0.6.44';
index e9df20ba30b247ee9dd0ea3d9d5d25183a75df22..0281aaf4bd97ff4d21ae791a511fa0d1360b62ef 100644 (file)
@@ -39,6 +39,7 @@
     "avatar": "Avatar",
     "upload_avatar": "Upload Avatar",
     "show_avatars": "Show Avatars",
+    "show_context": "Show context",
     "formatting_help": "formatting help",
     "sorting_help": "sorting help",
     "view_source": "view source",