Compare commits

..

488 commits

Author SHA1 Message Date
HJ
96a24ec625 Merge branch 'fix-discoverable-setting' into 'develop'
fix "allow discovery" setting misbehaving

See merge request pleroma/pleroma-fe!1518
2022-04-29 07:56:18 +00:00
Henry Jameson
91f833d1f0 fix "allow discovery" setting misbehaving 2022-04-29 10:52:16 +03:00
HJ
92b04ba7df Merge branch 'fix-chat-errors' into 'develop'
fix some chat errors/warnings that sometimes happen

See merge request pleroma/pleroma-fe!1515
2022-04-20 17:27:01 +00:00
Henry Jameson
895eda3714 fix some chat errors/warnings that sometimes happen 2022-04-20 20:19:22 +03:00
HJ
fb63e81ed1 Merge branch 'from/develop/tusooa/1157-popover-trigger' into 'develop'
Fix incorrect close of a status popover when clicking Expand inside it

Closes #1157

See merge request pleroma/pleroma-fe!1514
2022-04-20 09:15:18 +00:00
Tusooa Zhu
a4ea0a30bf
Fix incorrect close of a status popover when clicking Expand inside it
basically Vue (3 in particular?) will make changes to DOM before this event
listener is called, and if the target is displayed using v-if, it will not
be part of the DOM at that time, and contains() will return false. so it
goes to call hidePopover() which caused this bug.
2022-04-19 20:24:24 -04:00
HJ
1041a38f14 Merge branch 'fix-hashtags' into 'develop'
fix hashtags by explicitly putting attributes

See merge request pleroma/pleroma-fe!1513
2022-04-12 16:09:33 +00:00
Henry Jameson
0bb69d7fe0 fix tests 2022-04-12 19:04:32 +03:00
Henry Jameson
d175e86901 fix hashtags by explicitly putting attributes 2022-04-12 18:10:19 +03:00
HJ
c2a4051d72 Merge branch 'from/develop/tusooa/cropper-close' into 'develop'
Fix image cropper not closing correctly

See merge request pleroma/pleroma-fe!1512
2022-04-10 17:40:52 +00:00
Tusooa Zhu
169d13680a
Fix image cropper not closing correctly 2022-04-10 13:02:45 -04:00
HJ
4d15cbcbbd Merge branch 'from/develop/tusooa/1158-hidden-tabs' into 'develop'
Fix tab switcher not working when some tabs hidden

Closes #1158

See merge request pleroma/pleroma-fe!1511
2022-04-10 06:32:41 +00:00
Tusooa Zhu
3b02566e16
Fix tab switcher not working when some tabs hidden 2022-04-09 23:50:29 -04:00
HJ
87311cff09 Merge branch 'from/develop/tusooa/mobile-nav-link-col' into 'develop'
Fix mobile nav link text colour

See merge request pleroma/pleroma-fe!1510
2022-04-07 21:12:17 +00:00
Tusooa Zhu
6951fda0d6
Fix mobile nav link text colour 2022-04-07 15:59:03 -04:00
HJ
2d99cbc640 Merge branch 'from/develop/tusooa/1160-rm-shrug' into 'develop'
Fix shrug text in muted status

Closes #1160

See merge request pleroma/pleroma-fe!1509
2022-04-07 15:50:51 +00:00
Tusooa Zhu
22c70ae22a
Fix shrug text in muted status 2022-04-07 11:45:23 -04:00
HJ
d8324dd80b Merge branch 'from/develop/tusooa/shoutbox-icon-paneltext' into 'develop'
Use panel text instead of text for shoutbox icon

See merge request pleroma/pleroma-fe!1508
2022-04-07 07:24:40 +00:00
HJ
caacaf238c Merge branch 'from/develop/tusooa/popover-in-panel-style' into 'develop'
Fix popover in panel header styling

See merge request pleroma/pleroma-fe!1507
2022-04-07 07:24:22 +00:00
HJ
e4b8aaece6 Merge branch 'from/develop/tusooa/fix-shout-local' into 'develop'
Fix phoenix sockets in dev mode

See merge request pleroma/pleroma-fe!1506
2022-04-07 07:23:06 +00:00
HJ
bfc7b6af8f Merge branch 'from/develop/tusooa/1156-vue3-shoutbox' into 'develop'
Fix no reactivity on vuex 4 values

Closes #1156

See merge request pleroma/pleroma-fe!1505
2022-04-07 07:01:09 +00:00
Tusooa Zhu
fce9c5eeb2
Fix active popover style 2022-04-06 22:50:46 -04:00
Tusooa Zhu
6de87e8b65
Use panel text instead of text for shoutbox icon 2022-04-06 20:30:23 -04:00
Tusooa Zhu
041c72b07c
Fix dropdown menu style inside panel header 2022-04-06 20:14:17 -04:00
Tusooa Zhu
741a59e0cc
Fix phoenix sockets in dev mode
phoenix requires the Origin header to be set to the actual address,
so "http://localhost:xxxx" will not work.
2022-04-06 18:52:16 -04:00
Tusooa Zhu
4ddb6189dc
Fix no reactivity on vuex 4 values 2022-04-06 17:17:47 -04:00
HJ
0e56ac1c2b Merge branch 'fix-csp-vue3' into 'develop'
Makes develop usable on stock PleromaBE

See merge request pleroma/pleroma-fe!1504
2022-04-06 15:48:33 +00:00
Henry Jameson
853f5145be fix tegulu 2022-04-06 18:43:47 +03:00
Henry Jameson
b213d25711 heck 2022-04-06 15:48:07 +03:00
Henry Jameson
bd77f3a1a6 fix i18n for good?? 2022-04-06 15:45:44 +03:00
Henry Jameson
78817e37f7 force runtime build of i18n 2022-04-06 11:43:30 +03:00
Henry Jameson
2b0dd2cbae fix CSP by compiling the i18n templates as well 2022-04-05 23:45:26 +03:00
HJ
a613447105 Merge branch 'from/develop/tusooa/phoenix-1.6.2' into 'develop'
Fix phoenix at 1.6.2

See merge request pleroma/pleroma-fe!1502
2022-04-01 13:03:13 +00:00
Tusooa Zhu
a3233e31d0
Fix phoenix at 1.6.2
According to https://github.com/phoenixframework/phoenix/issues/4623 ,
1.6.2 seems to not have the disconnection bug.
2022-04-01 08:44:18 -04:00
HJ
f71f101fce Merge branch 'vue3-again' into 'develop'
Migration to Vue 3 (again)

See merge request pleroma/pleroma-fe!1385
2022-03-31 17:45:29 +00:00
HJ
afdc61b9b7 Merge branch 'vue3-no-compat' into 'vue3-again'
Remove Vue3 compat build

See merge request pleroma/pleroma-fe!1500
2022-03-31 17:39:08 +00:00
HJ
1d1ea7e703 Merge branch 'from/develop/tusooa/fix-feat-shoutbox-en' into 'develop'
Fix English translation of Shoutbox in features panel

See merge request pleroma/pleroma-fe!1489
2022-03-30 21:40:19 +00:00
Henry Jameson
052ad2fe3f Merge branch 'vue3-again' into vue3-no-compat
* vue3-again:
  oops
2022-03-30 23:54:21 +03:00
Henry Jameson
a0099ecb66 oops 2022-03-30 23:54:11 +03:00
Henry Jameson
9940739f1f Merge branch 'vue3-again' into vue3-no-compat
* vue3-again:
  fix some mishaps i noticed during self-review
2022-03-30 23:52:24 +03:00
Henry Jameson
afbe1a96ac fix some mishaps i noticed during self-review 2022-03-30 23:48:06 +03:00
Henry Jameson
e029c2864f Merge branch 'vue3-again' into vue3-no-compat
* vue3-again:
  fix importer
2022-03-30 18:00:48 +03:00
Henry Jameson
dadf2f407f fix importer 2022-03-30 18:00:37 +03:00
Henry Jameson
6751c22a23 Merge branch 'vue3-again' into vue3-no-compat
* vue3-again:
  make all clickable icons into actual buttons
2022-03-30 13:13:41 +03:00
Henry Jameson
1943991077 make all clickable icons into actual buttons 2022-03-30 12:34:27 +03:00
Henry Jameson
115170f35d Merge branch 'vue3-again' into vue3-no-compat
* vue3-again:
  fix emoji input warning spam
  fix error clear icon
  Add controlledShowingLongSubject and toggle in StatusContent props
2022-03-30 01:26:35 +03:00
Henry Jameson
c3690b456e fix emoji input warning spam 2022-03-30 01:24:53 +03:00
Henry Jameson
70593e71e6 fix error clear icon 2022-03-30 01:24:17 +03:00
HJ
1c60609547 Merge branch 'vue3-again' into 'vue3-again'
Add controlledShowingLongSubject and toggle in StatusContent props

See merge request pleroma/pleroma-fe!1499
2022-03-29 18:13:27 +00:00
Tusooa Zhu
b84acfd7b7
Add controlledShowingLongSubject and toggle in StatusContent props 2022-03-29 13:56:42 -04:00
Henry Jameson
97e072d93a how did this get back?? 2022-03-29 20:23:44 +03:00
Henry Jameson
2179054384 Merge branch 'vue3-again' into vue3-no-compat
* vue3-again:
  fix warning about custom component
  fix?
  fix some issues with trees
  removing uselsess stuff
  fix reset buttons in profile again
  fix spacing in poll expiration label
  registration fixes
2022-03-29 20:00:59 +03:00
Henry Jameson
a3f48fc3f4 fix warning about custom component 2022-03-29 19:55:30 +03:00
Henry Jameson
de2f968645 fix? 2022-03-29 19:44:07 +03:00
Henry Jameson
e1483488c7 fix some issues with trees 2022-03-29 19:23:30 +03:00
Henry Jameson
a1822f073d removing uselsess stuff 2022-03-29 19:21:13 +03:00
Henry Jameson
218b15b5fd fix reset buttons in profile again 2022-03-29 19:12:57 +03:00
Henry Jameson
bc029b0fa2 fix spacing in poll expiration label 2022-03-29 19:04:01 +03:00
Henry Jameson
2d7f242713 remove unused props to fix test 2022-03-29 16:33:34 +03:00
Henry Jameson
0afc955ebd registration fixes 2022-03-29 16:08:57 +03:00
Henry Jameson
e80e53d9d2 Merge branch 'vue3-again' into vue3-no-compat
* vue3-again:
  re-fix i18n warnings again
  fix qr code
2022-03-29 16:00:56 +03:00
Henry Jameson
afcfcce2e6 re-fix i18n warnings again 2022-03-29 15:46:21 +03:00
Henry Jameson
3171241c6f fix qr code 2022-03-29 15:44:42 +03:00
Henry Jameson
3e0bb91ff2 Merge branch 'vue3-again' into vue3-no-compat
* vue3-again:
  lint + fixes for registration
  fix production build's reply not working in tree mode
  lock down version?
  manual lint
  Revert "fix weird thing i somehow missed"
  Revert "lint"
  fix weird thing i somehow missed
  lint
2022-03-29 15:35:47 +03:00
Henry Jameson
5bbc0e0bb5 lint + fixes for registration 2022-03-29 15:35:18 +03:00
Henry Jameson
3799983d4f fix production build's reply not working in tree mode 2022-03-29 12:43:42 +03:00
Henry Jameson
3f04ebd89a lock down version? 2022-03-29 12:27:59 +03:00
Henry Jameson
b93e5437bd manual lint 2022-03-29 12:04:09 +03:00
Henry Jameson
64d56e6515 Revert "fix weird thing i somehow missed"
This reverts commit b132581b3a.
2022-03-29 11:59:00 +03:00
Henry Jameson
d9d4d8954e Revert "lint"
This reverts commit f20ae34400.
2022-03-29 11:57:54 +03:00
Henry Jameson
b132581b3a fix weird thing i somehow missed 2022-03-29 11:47:22 +03:00
Henry Jameson
f20ae34400 lint 2022-03-29 01:04:37 +03:00
Henry Jameson
dc8bef7928 remove compat build 2022-03-29 00:58:17 +03:00
Henry Jameson
650e69c336 fix misc warnings 2022-03-29 00:02:02 +03:00
Henry Jameson
f21dc21a83 properly implement resettableAsyncComponent 2022-03-28 23:55:57 +03:00
Henry Jameson
9afbb12f95 fix opacity control again 2022-03-28 23:55:11 +03:00
Henry Jameson
7b10e47b21 rename some binding hooks according to new names 2022-03-28 23:54:30 +03:00
Henry Jameson
4b630c3c36 fix warnings 2022-03-28 17:37:26 +03:00
Henry Jameson
bdd240a230 fix some more warnings 2022-03-28 17:21:42 +03:00
Henry Jameson
c57af7e242 remove some warnings 2022-03-28 17:13:48 +03:00
Henry Jameson
f706234d77 fix being unable to set/reset background (and possibly avatar/banner) 2022-03-28 15:01:34 +03:00
Henry Jameson
805615d52b fix background not showing 2022-03-28 14:26:50 +03:00
Henry Jameson
8424b772b0 fix tabs in search 2022-03-27 14:43:16 +03:00
Henry Jameson
115f38c422 fix optional color inputs 2022-03-27 14:20:55 +03:00
Henry Jameson
48fd8a66ad fix opacity control 2022-03-27 14:18:02 +03:00
Henry Jameson
53cde52027 fix font control 2022-03-27 14:16:23 +03:00
Henry Jameson
75f6506bc7 fix (You) spacing 2022-03-27 14:10:45 +03:00
Henry Jameson
1d77063a4b fix shadow control in theme tab 2022-03-27 13:45:02 +03:00
Henry Jameson
ccd7378347 fix (roundness) ranges in theme tab 2022-03-27 13:31:56 +03:00
Henry Jameson
8e711e0587 get rid of portal-vue 2022-03-27 12:59:15 +03:00
Henry Jameson
87d420a92b port !1488 to vue3 2022-03-27 12:58:28 +03:00
Henry Jameson
d6bbccdd71 Merge remote-tracking branch 'origin/develop' into vue3-again
* origin/develop:
  Remove debugging code
  Fix overlapping buttons in Theme settings
  Update dependency ruffle-mirror to v2021.12.31
  Update dependency babel-loader to v8.2.4
2022-03-27 12:50:00 +03:00
Henry Jameson
913749739f fix user-list-popover 2022-03-27 12:29:28 +03:00
Henry Jameson
0b7a8dca15 fix duplicate setting (most likely a merge issue) 2022-03-27 12:26:51 +03:00
Henry Jameson
dd4672dc9a fix opening directly to filtering tab not working 2022-03-27 12:24:38 +03:00
Henry Jameson
9ac7046521 Fix notices not disappearing on their own 2022-03-27 12:21:33 +03:00
Henry Jameson
e4c804fac0 fix another spacing issue 2022-03-27 12:03:31 +03:00
HJ
fd77c583bf Merge branch 'from/develop/tusooa/fix-overlap-button' into 'develop'
Fix overlapping buttons in Theme settings

See merge request pleroma/pleroma-fe!1488
2022-03-25 13:22:06 +00:00
HJ
b319c0c72b Remove debugging code 2022-03-25 13:17:22 +00:00
Tusooa Zhu
c5551e834b
Fix English translation of Shoutbox in features panel 2022-03-24 19:28:15 -04:00
Tusooa Zhu
e58422889b
Fix overlapping buttons in Theme settings 2022-03-24 18:03:13 -04:00
Henry Jameson
b7755314b1 fix forms closing in timelines 2022-03-24 14:09:25 +02:00
Henry Jameson
3fb647b34b fix minor renames 2022-03-24 13:50:22 +02:00
Henry Jameson
0eb9c019e4 woah ima stupid 2022-03-24 13:41:52 +02:00
HJ
aa0b2e0723 Merge branch 'renovate/ruffle-mirror-2021.x' into 'develop'
Update dependency ruffle-mirror to v2021.12.31

See merge request pleroma/pleroma-fe!1486
2022-03-24 10:10:03 +00:00
Henry Jameson
4539feed40 fix checkboxes, specifically the NSFW one 2022-03-24 11:41:39 +02:00
Pleroma Renovate Bot
20c14a1d99 Update dependency ruffle-mirror to v2021.12.31 2022-03-24 09:05:00 +00:00
Henry Jameson
9793002070 cleanup console log 2022-03-23 16:53:57 +02:00
Henry Jameson
01d8fa4e54 fix i18n at places 2022-03-23 16:32:53 +02:00
Henry Jameson
961ca3a71b fix all the spacings i could find 2022-03-23 16:15:05 +02:00
Henry Jameson
08811e5a27 fix spacings in notifications 2022-03-23 16:08:45 +02:00
Henry Jameson
a6fae395da fix dupe id 2022-03-23 16:05:53 +02:00
Henry Jameson
d690b88c1c fix animations 2022-03-23 15:53:36 +02:00
Henry Jameson
322ec8681a cleanup 2022-03-23 15:44:37 +02:00
HJ
e1bfa6fbd3 Merge branch 'renovate/babel-monorepo' into 'develop'
Update dependency babel-loader to v8.2.4

See merge request pleroma/pleroma-fe!1484
2022-03-23 13:04:33 +00:00
Pleroma Renovate Bot
db0e1a2534 Update dependency babel-loader to v8.2.4 2022-03-23 09:05:04 +00:00
Henry Jameson
d524e98348 fix capitalization (and localization of tooltips for scope icon) 2022-03-22 20:42:29 +02:00
Henry Jameson
7afa6c9f40 listeners aren't actually used 2022-03-22 20:22:28 +02:00
Henry Jameson
538903f9d8 fix selects in settings screen 2022-03-22 20:17:25 +02:00
Henry Jameson
c5a6f40dff fix tabs not being able to be "disabled" 2022-03-22 20:15:21 +02:00
Henry Jameson
b817e09ee8 fix avatars not opening inline card 2022-03-22 19:43:11 +02:00
Henry Jameson
6b5791fda6 fix other weird route 2022-03-22 19:20:12 +02:00
Henry Jameson
3250e59266 fix routes test 2022-03-22 18:56:54 +02:00
Henry Jameson
e5ae0671ce skip user profile test for now https://github.com/vuejs/test-utils/issues/1382 2022-03-22 18:56:39 +02:00
Henry Jameson
9d7a7e2019 fix emoji input tests 2022-03-22 18:22:23 +02:00
Henry Jameson
c2cf13fc00 fix richcontent and its tests 2022-03-22 18:22:23 +02:00
Henry Jameson
c3546ea856 fix tests running 2022-03-22 18:22:23 +02:00
Henry Jameson
edb66ecade fix mobile post button being too square 2022-03-22 16:39:27 +02:00
Henry Jameson
c6a4a0a320 Merge remote-tracking branch 'origin/develop' into vue3-again
* origin/develop:
  quick fix for alignment in avatars, juggling multiple branches is confusing
  fix even more issues with avatars
  Update dependency ora to v0.4.1
  Update dependency mini-css-extract-plugin to v0.12.0
  Update dependency karma-firefox-launcher to v1.3.0
  fix avatars in mobile view
2022-03-22 12:14:02 +02:00
HJ
6a31962ca0 Merge branch 'fix-avatars3' into 'develop'
quick fix for alignment in avatars

See merge request pleroma/pleroma-fe!1483
2022-03-22 10:03:17 +00:00
Henry Jameson
fa99abf106 quick fix for alignment in avatars, juggling multiple branches is confusing 2022-03-22 11:57:21 +02:00
HJ
9be06d9f71 Merge branch 'renovate/mini-css-extract-plugin-0.x' into 'develop'
Update dependency mini-css-extract-plugin to v0.12.0

See merge request pleroma/pleroma-fe!1480
2022-03-22 09:54:23 +00:00
HJ
966919874e Merge branch 'fix-avatars2' into 'develop'
fix even more issues with avatars

See merge request pleroma/pleroma-fe!1482
2022-03-22 09:53:13 +00:00
HJ
a253c95170 Merge branch 'develop' into 'fix-avatars2'
# Conflicts:
#   src/components/status/status.scss
2022-03-22 09:47:24 +00:00
Henry Jameson
c6e0dcf08e fix even more issues with avatars 2022-03-22 11:43:51 +02:00
HJ
0147226b3d Merge branch 'renovate/ora-0.x' into 'develop'
Update dependency ora to v0.4.1

See merge request pleroma/pleroma-fe!1481
2022-03-22 09:20:54 +00:00
HJ
1571053fba Merge branch 'renovate/karma-firefox-launcher-1.x' into 'develop'
Update dependency karma-firefox-launcher to v1.3.0

See merge request pleroma/pleroma-fe!1475
2022-03-22 09:20:11 +00:00
Pleroma Renovate Bot
623aa3d20d Update dependency ora to v0.4.1 2022-03-22 09:06:15 +00:00
Pleroma Renovate Bot
636a55e72d Update dependency mini-css-extract-plugin to v0.12.0 2022-03-22 09:06:08 +00:00
Pleroma Renovate Bot
304f871332 Update dependency karma-firefox-launcher to v1.3.0 2022-03-22 09:06:00 +00:00
HJ
2cd8a3ec8d Merge branch 'fix-mobile-avatars' into 'develop'
fix avatars in mobile view

See merge request pleroma/pleroma-fe!1479
2022-03-22 08:24:42 +00:00
Henry Jameson
971cec024c fix avatars in mobile view 2022-03-22 10:20:45 +02:00
Henry Jameson
7a17eb7fec fix selects 2022-03-21 22:01:08 +02:00
Henry Jameson
4cb14c257c fix expert mode checkbox not working 2022-03-21 21:34:55 +02:00
Henry Jameson
1187727b60 fix tabswitcher bugs 2022-03-21 21:29:51 +02:00
Henry Jameson
54fd7e2be6 Merge remote-tracking branch 'origin/develop' into vue3-again
* origin/develop:
  improve the looks of bot indicator
  fix bot indicator appearing on retweeter avatar
  Update dependency localforage to v1.10.0
  Update dependency http-proxy-middleware to v0.21.0
  Update dependency eslint-plugin-standard to v4.1.0
  Update dependency eslint-plugin-import to v2.25.4
  Update babel monorepo to v7.17.8
  Update dependency iso-639-1 to v2.1.13
  Update dependency express to v4.17.3
  Update dependency eslint-plugin-promise to v4.3.1
  Update dependency eslint-loader to v2.2.1
2022-03-21 21:09:48 +02:00
HJ
0ef58696bf Merge branch 'bot-indicator-fixes' into 'develop'
Bot indicator fixes

See merge request pleroma/pleroma-fe!1477
2022-03-21 19:04:21 +00:00
Henry Jameson
9478a462a7 improve the looks of bot indicator 2022-03-21 20:59:25 +02:00
Henry Jameson
4e2fd7baf9 fix bot indicator appearing on retweeter avatar 2022-03-21 20:39:56 +02:00
HJ
66fb3987d6 Merge branch 'renovate/localforage-1.x' into 'develop'
Update dependency localforage to v1.10.0

See merge request pleroma/pleroma-fe!1476
2022-03-21 18:27:24 +00:00
HJ
1e60a491c4 Merge branch 'renovate/eslint-plugin-standard-4.x' into 'develop'
Update dependency eslint-plugin-standard to v4.1.0

See merge request pleroma/pleroma-fe!1471
2022-03-21 18:26:27 +00:00
HJ
12ce58a5a0 Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo to v7.17.8

See merge request pleroma/pleroma-fe!1469
2022-03-21 18:25:29 +00:00
HJ
2f24f3312d Merge branch 'renovate/http-proxy-middleware-0.x' into 'develop'
Update dependency http-proxy-middleware to v0.21.0

See merge request pleroma/pleroma-fe!1473
2022-03-21 18:24:02 +00:00
HJ
1cc35b6df8 Merge branch 'renovate/eslint-plugin-import-2.x' into 'develop'
Update dependency eslint-plugin-import to v2.25.4

See merge request pleroma/pleroma-fe!1468
2022-03-21 18:19:24 +00:00
Pleroma Renovate Bot
4f2be206df Update dependency localforage to v1.10.0 2022-03-21 18:16:34 +00:00
Pleroma Renovate Bot
6074ad67ab Update dependency http-proxy-middleware to v0.21.0 2022-03-21 18:16:06 +00:00
Pleroma Renovate Bot
c5cb76ac3b Update dependency eslint-plugin-standard to v4.1.0 2022-03-21 18:15:53 +00:00
Pleroma Renovate Bot
e4b010321d Update dependency eslint-plugin-import to v2.25.4 2022-03-21 18:15:39 +00:00
Pleroma Renovate Bot
5e4ff5de7c Update babel monorepo to v7.17.8 2022-03-21 18:15:22 +00:00
HJ
6b14b645be Merge branch 'renovate/eslint-loader-2.x' into 'develop'
Update dependency eslint-loader to v2.2.1

See merge request pleroma/pleroma-fe!1467
2022-03-21 18:13:00 +00:00
HJ
79258f61be Merge branch 'renovate/eslint-plugin-promise-4.x' into 'develop'
Update dependency eslint-plugin-promise to v4.3.1

See merge request pleroma/pleroma-fe!1470
2022-03-21 18:10:10 +00:00
HJ
3ea442667f Merge branch 'renovate/express-4.x' into 'develop'
Update dependency express to v4.17.3

See merge request pleroma/pleroma-fe!1472
2022-03-21 18:05:16 +00:00
HJ
6c120cc576 Merge branch 'renovate/iso-639-1-2.x' into 'develop'
Update dependency iso-639-1 to v2.1.13

See merge request pleroma/pleroma-fe!1474
2022-03-21 18:01:48 +00:00
Pleroma Renovate Bot
89d6e624f2 Update dependency iso-639-1 to v2.1.13 2022-03-21 09:07:38 +00:00
Pleroma Renovate Bot
060aa41f35 Update dependency express to v4.17.3 2022-03-20 09:05:19 +00:00
Pleroma Renovate Bot
67f5cf3c03 Update dependency eslint-plugin-promise to v4.3.1 2022-03-19 09:07:04 +00:00
Henry Jameson
5948d20f00 mutes and blocks tab works 2022-03-18 13:36:08 +02:00
Henry Jameson
b3ed29ff02 made withLoadMore work... sorta 2022-03-18 13:32:36 +02:00
Pleroma Renovate Bot
5d973df5bd Update dependency eslint-loader to v2.2.1 2022-03-18 09:04:52 +00:00
Henry Jameson
26bfbdc2ad fix sw compilation 2022-03-18 11:02:00 +02:00
Henry Jameson
50ea6dd142 Merge remote-tracking branch 'origin/develop' into vue3-again
* origin/develop:
  Update dependency cropperjs to v1.5.12
  Update dependency body-scroll-lock to v2.7.1
  Update babel monorepo
2022-03-18 10:59:28 +02:00
HJ
6f1d953642 Merge branch 'renovate/cropperjs-1.x' into 'develop'
Update dependency cropperjs to v1.5.12

See merge request pleroma/pleroma-fe!1465
2022-03-18 08:58:03 +00:00
HJ
86f1b88d79 Merge branch 'renovate/body-scroll-lock-2.x' into 'develop'
Update dependency body-scroll-lock to v2.7.1

See merge request pleroma/pleroma-fe!1464
2022-03-18 08:57:08 +00:00
HJ
4610b6e547 Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo

See merge request pleroma/pleroma-fe!1462
2022-03-17 09:11:07 +00:00
Pleroma Renovate Bot
be02516776 Update dependency cropperjs to v1.5.12 2022-03-17 09:05:41 +00:00
Pleroma Renovate Bot
4e100b9ea2 Update dependency body-scroll-lock to v2.7.1 2022-03-17 09:05:34 +00:00
Pleroma Renovate Bot
8ae1f7e192 Update babel monorepo 2022-03-17 09:05:20 +00:00
Henry Jameson
fea0c91f74 bunp node version 2022-03-17 09:33:36 +02:00
Henry Jameson
051d51bcd9 fix suggest not working 2022-03-17 09:28:19 +02:00
Henry Jameson
5718c6491e fix selects being messed up 2022-03-17 09:06:05 +02:00
Henry Jameson
a97c07bfdf fix settings not persisting 2022-03-17 09:02:26 +02:00
Henry Jameson
d815f984fb fix i18n errors related to @ symbol 2022-03-17 08:53:45 +02:00
Henry Jameson
4993dc37e2 fix rich content not rendering stillimage nor links correctly 2022-03-17 08:53:45 +02:00
Henry Jameson
0671aa0dd0 fix tabswitcher 2022-03-17 08:53:45 +02:00
Henry Jameson
b62653c202 fix chat user titles 2022-03-17 08:37:01 +02:00
Henry Jameson
be4244acde fix some warnings 2022-03-17 08:35:19 +02:00
Henry Jameson
8311d4deba shit renders yo 2022-03-16 22:13:21 +02:00
Henry Jameson
e51144809f shit boots yo 2022-03-16 22:02:44 +02:00
Henry Jameson
cd4ad2df11 Merge remote-tracking branch 'origin/develop' into vue3-again
* origin/develop: (475 commits)
  Apply 1 suggestion(s) to 1 file(s)
  Update dependency @ungap/event-target to v0.2.3
  Update package.json
  fix broken icons after FA upgrade
  Update Font Awesome
  Update dependency webpack-dev-middleware to v3.7.3
  Update dependency vuelidate to v0.7.7
  Pin dependency @kazvmoe-infra/pinch-zoom-element to 1.2.0
  lint
  Make media modal buttons larger
  Add English translation for hide tooltip
  Add hide button to media modal
  Lint
  Prevent hiding media viewer if swiped over SwipeClick
  Fix webkit image blurs
  Fix video in media modal not displaying properly
  Add changelog for https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403
  Remove image box-shadow in media modal
  Clean up debug code for image pinch zoom
  Bump @kazvmoe-infra/pinch-zoom-element to 1.2.0 on npm
  ...
2022-03-16 21:00:20 +02:00
HJ
b632d740c1 Merge branch 'expert-settings-and-serverside' into 'develop'
Expert settings and serverside settings + new defaults

See merge request pleroma/pleroma-fe!1438
2022-03-16 17:33:24 +00:00
HJ
123de8aad0 Apply 1 suggestion(s) to 1 file(s) 2022-03-16 17:02:09 +00:00
HJ
186078ae2a Merge branch 'renovate/ungap-event-target-0.x' into 'develop'
Update dependency @ungap/event-target to v0.2.3

See merge request pleroma/pleroma-fe!1463
2022-03-16 09:15:24 +00:00
Pleroma Renovate Bot
23c80794d5 Update dependency @ungap/event-target to v0.2.3 2022-03-16 09:05:13 +00:00
HJ
3e1415ef89 Merge branch 'hj-develop-patch-28607' into 'develop'
Fix build due to github shenanigans

See merge request pleroma/pleroma-fe!1461
2022-03-15 19:07:02 +00:00
HJ
2439f55342 Merge branch 'renovate/font-awesome' into 'develop'
Update Font Awesome

See merge request pleroma/pleroma-fe!1460
2022-03-15 19:03:44 +00:00
HJ
b0babee98a Update package.json 2022-03-15 19:02:34 +00:00
Henry Jameson
47bb08514b fix broken icons after FA upgrade 2022-03-15 21:00:52 +02:00
HJ
60eb164bdc Merge branch 'renovate/webpack-dev-middleware-3.x' into 'develop'
Update dependency webpack-dev-middleware to v3.7.3

See merge request pleroma/pleroma-fe!1459
2022-03-15 18:40:58 +00:00
Pleroma Renovate Bot
3101456a85 Update Font Awesome 2022-03-15 09:05:20 +00:00
Pleroma Renovate Bot
5e851e643b Update dependency webpack-dev-middleware to v3.7.3 2022-03-15 09:05:08 +00:00
HJ
bfa62db274 Merge branch 'renovate/pin-dependencies' into 'develop'
Pin dependency @kazvmoe-infra/pinch-zoom-element to 1.2.0

See merge request pleroma/pleroma-fe!1457
2022-03-15 08:22:33 +00:00
HJ
b349bcd427 Merge branch 'renovate/vuelidate-0.x' into 'develop'
Update dependency vuelidate to v0.7.7

See merge request pleroma/pleroma-fe!1458
2022-03-14 09:11:28 +00:00
Pleroma Renovate Bot
7e6e6292f8 Update dependency vuelidate to v0.7.7 2022-03-14 09:05:17 +00:00
Pleroma Renovate Bot
34592fa63a Pin dependency @kazvmoe-infra/pinch-zoom-element to 1.2.0 2022-03-14 09:05:10 +00:00
Henry Jameson
71c0b59866 lint 2022-03-14 09:35:29 +02:00
Henry Jameson
a97db1efd6 Merge remote-tracking branch 'origin/develop' into expert-settings-and-serverside
* origin/develop: (83 commits)
  Make media modal buttons larger
  Add English translation for hide tooltip
  Add hide button to media modal
  Lint
  Prevent hiding media viewer if swiped over SwipeClick
  Fix webkit image blurs
  Fix video in media modal not displaying properly
  Add changelog for https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403
  Remove image box-shadow in media modal
  Clean up debug code for image pinch zoom
  Bump @kazvmoe-infra/pinch-zoom-element to 1.2.0 on npm
  Bump pinch-zoom-element version
  Clean up
  Check whether we swiped only for mouse pointer
  Scale swipe threshold with viewport width
  Update pinch-zoom-element
  Allow pinch-zoom to fill the whole screen
  Use native click for hiding overlay
  Reset position on swipe end even if we cannot navigate
  Make lint happy
  ...
2022-03-14 09:31:24 +02:00
HJ
e34d71fc1f Merge branch 'from/develop/tusooa/tree-threading' into 'develop'
Add the option to display threads as trees

See merge request pleroma/pleroma-fe!1407
2022-03-13 17:31:46 +00:00
HJ
89efb0d2f4 Merge branch 'from/develop/tusooa/media-touch-actions' into 'develop'
Be able to scroll and pan media in media modal

See merge request pleroma/pleroma-fe!1403
2022-03-13 17:22:30 +00:00
Tusooa Zhu
1b204012ea
Make media modal buttons larger 2022-03-13 12:02:03 -04:00
Tusooa Zhu
b67a557a8c
Add English translation for hide tooltip 2022-03-13 12:02:03 -04:00
Tusooa Zhu
5fb302d0f1
Add hide button to media modal 2022-03-13 12:02:02 -04:00
Tusooa Zhu
7e21853cca
Lint 2022-03-13 12:02:02 -04:00
Tusooa Zhu
7dd1a0dd30
Prevent hiding media viewer if swiped over SwipeClick 2022-03-13 12:02:02 -04:00
Tusooa Zhu
90b066a744
Fix webkit image blurs 2022-03-13 12:02:02 -04:00
Tusooa Zhu
1128cc463c
Fix video in media modal not displaying properly 2022-03-13 12:02:02 -04:00
Tusooa Zhu
22d8961c5e
Add changelog for https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403 2022-03-13 12:02:02 -04:00
Tusooa Zhu
76727cd39c
Remove image box-shadow in media modal
The box-shadow causes an image to be very blurry on Webkit browsers
(experienced: Konqueror, Safari; heard of: Chrome) when scaled up
if the initial size of the image is much smaller than the actual size
(e.g. when viewing a very long picture).

The shadow is not really obvious anyway.
2022-03-13 12:02:02 -04:00
Tusooa Zhu
5829cd98af
Clean up debug code for image pinch zoom 2022-03-13 12:02:02 -04:00
Tusooa Zhu
49fa9c47e9
Bump @kazvmoe-infra/pinch-zoom-element to 1.2.0 on npm
https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1403
2022-03-13 12:02:00 -04:00
Tusooa Zhu
495960c83a
Bump pinch-zoom-element version 2022-03-13 12:00:45 -04:00
Tusooa Zhu
3b4afdf567
Clean up 2022-03-13 12:00:45 -04:00
Tusooa Zhu
a485ebc2bb
Check whether we swiped only for mouse pointer 2022-03-13 12:00:45 -04:00
Tusooa Zhu
6980e4ddf1
Scale swipe threshold with viewport width 2022-03-13 12:00:44 -04:00
Tusooa Zhu
cb19db1006
Update pinch-zoom-element 2022-03-13 12:00:44 -04:00
Tusooa Zhu
ddf6c9cef0
Allow pinch-zoom to fill the whole screen 2022-03-13 12:00:44 -04:00
Tusooa Zhu
9f3a983fef
Use native click for hiding overlay
The pointerup strategy is unsuccessful, as some other overlays
(Firefox's Inspect Element) will pass down pointerup events.
2022-03-13 12:00:44 -04:00
Tusooa Zhu
839627ffc4
Reset position on swipe end even if we cannot navigate 2022-03-13 12:00:44 -04:00
Tusooa Zhu
f3269cdc10
Make lint happy 2022-03-13 12:00:44 -04:00
Tusooa Zhu
3502d374e3
Prevent the click event from firing on content below modal 2022-03-13 12:00:44 -04:00
Tusooa Zhu
0190a36070
Add missing swipe click component 2022-03-13 12:00:44 -04:00
Tusooa Zhu
23a6b86ef3
Clean up 2022-03-13 12:00:44 -04:00
Tusooa Zhu
29cd8fbd3b
Add swipe-click handler to media modal
Now swiping will correctly change the current media, and with a good
preview. Clicking without swiping closes the overlay.
2022-03-13 12:00:44 -04:00
Tusooa Zhu
a36673a6a8
Use pinch-zoom-element for pinch zoom functionality 2022-03-13 12:00:41 -04:00
Tusooa Zhu
544db06294
Add pan threshold 2022-03-13 11:56:35 -04:00
Tusooa Zhu
d9030b4fdd
Handle pinch action 2022-03-13 11:56:35 -04:00
Tusooa Zhu
a7570f5eb2
Preview swipe action 2022-03-13 11:56:34 -04:00
Tusooa Zhu
f96e5882d1
Make media modal be aware of multi-touch actions
Originally the media viewer would think every touch is a swipe (one-finger
touch event), so we would encounter the case where a two-finger scale event
would incorrectly change the current media. This is now fixed.
2022-03-13 11:56:30 -04:00
Henry Jameson
f16f35a4d4 Merge remote-tracking branch 'origin/develop' into expert-settings-and-serverside
* origin/develop:
  Update dependency v-click-outside to v2.1.5
  Update dependency shelljs to v0.8.5
  Update dependency portal-vue to v2.1.7
  Update dependency lodash to v4.17.21
  Update dependency karma-spec-reporter to v0.0.33
  Update dependency karma-webpack to v4.0.2
  Update dependency karma-sourcemap-loader to v0.3.8
  Update dependency eslint-plugin-vue to v5.2.3
  Update dependency chromedriver to v87.0.7
  Update dependency @chenfengyuan/vue-qrcode to v1.0.2
  Pin dependencies
  Pin dependencies
  Do not mute bot posts in notifications
  Lint
  Add renovate.json
  Add bot indication to user icon on statuses
  Mute bot posts
  fix placeholder attachments opening new tab
2022-03-13 16:34:05 +02:00
HJ
51b14cc615 Merge branch 'eientei' into 'develop'
Mute bot posts filtering option

See merge request pleroma/pleroma-fe!1440
2022-03-13 14:04:15 +00:00
HJ
80bd6433aa Merge branch 'renovate/shelljs-0.x' into 'develop'
Update dependency shelljs to v0.8.5

See merge request pleroma/pleroma-fe!1455
2022-03-13 10:20:28 +00:00
HJ
50b3a30d1a Merge branch 'renovate/v-click-outside-2.x' into 'develop'
Update dependency v-click-outside to v2.1.5

See merge request pleroma/pleroma-fe!1456
2022-03-13 09:32:59 +00:00
Henry Jameson
5930b667a1 reduce the copypaste by making it more functional-style 2022-03-13 11:30:38 +02:00
Pleroma Renovate Bot
9b5ae4d4a3 Update dependency v-click-outside to v2.1.5 2022-03-13 09:04:54 +00:00
Pleroma Renovate Bot
22c8ad4583 Update dependency shelljs to v0.8.5 2022-03-13 09:04:46 +00:00
Henry Jameson
726fdbea15 remove "experimental" label from streaming api 2022-03-13 10:51:13 +02:00
HJ
67c2b52682 Merge branch 'renovate/portal-vue-2.x' into 'develop'
Update dependency portal-vue to v2.1.7

See merge request pleroma/pleroma-fe!1452
2022-03-13 08:20:20 +00:00
HJ
238e557d2b Merge branch 'renovate/lodash-monorepo' into 'develop'
Update dependency lodash to v4.17.21

See merge request pleroma/pleroma-fe!1451
2022-03-13 08:19:32 +00:00
HJ
c8d87e7ef9 Merge branch 'renovate/karma-spec-reporter-0.x' into 'develop'
Update dependency karma-spec-reporter to v0.0.33

See merge request pleroma/pleroma-fe!1449
2022-03-12 09:30:01 +00:00
Pleroma Renovate Bot
a83b07a629 Update dependency portal-vue to v2.1.7 2022-03-12 09:07:30 +00:00
Pleroma Renovate Bot
3bd2f3c36d Update dependency lodash to v4.17.21 2022-03-12 09:07:19 +00:00
Pleroma Renovate Bot
465c5f194c Update dependency karma-spec-reporter to v0.0.33 2022-03-12 09:07:05 +00:00
HJ
ec3df8e5f2 Merge branch 'renovate/chromedriver-87.x' into 'develop'
Update dependency chromedriver to v87.0.7

See merge request pleroma/pleroma-fe!1446
2022-03-11 10:59:22 +00:00
HJ
c194568daf Merge branch 'renovate/eslint-plugin-vue-5.x' into 'develop'
Update dependency eslint-plugin-vue to v5.2.3

See merge request pleroma/pleroma-fe!1447
2022-03-11 10:59:08 +00:00
HJ
63039aea37 Merge branch 'renovate/chenfengyuan-vue-qrcode-1.x' into 'develop'
Update dependency @chenfengyuan/vue-qrcode to v1.0.2

See merge request pleroma/pleroma-fe!1445
2022-03-11 09:15:37 +00:00
HJ
fb803b2ff2 Merge branch 'renovate/karma-webpack-4.x' into 'develop'
Update dependency karma-webpack to v4.0.2

See merge request pleroma/pleroma-fe!1450
2022-03-11 09:13:27 +00:00
HJ
fabac8524d Merge branch 'renovate/karma-sourcemap-loader-0.x' into 'develop'
Update dependency karma-sourcemap-loader to v0.3.8

See merge request pleroma/pleroma-fe!1448
2022-03-11 09:12:42 +00:00
Pleroma Renovate Bot
35ad2a1957 Update dependency karma-webpack to v4.0.2 2022-03-11 09:06:09 +00:00
Pleroma Renovate Bot
e4d40fbba4 Update dependency karma-sourcemap-loader to v0.3.8 2022-03-11 09:05:47 +00:00
Pleroma Renovate Bot
d4076e5df5 Update dependency eslint-plugin-vue to v5.2.3 2022-03-11 09:05:34 +00:00
Pleroma Renovate Bot
2ced27e734 Update dependency chromedriver to v87.0.7 2022-03-11 09:05:21 +00:00
Pleroma Renovate Bot
04d4f5525c Update dependency @chenfengyuan/vue-qrcode to v1.0.2 2022-03-11 09:05:09 +00:00
HJ
949f11ea1e Merge branch 'renovate/pin-dependencies' into 'develop'
Pin dependencies

See merge request pleroma/pleroma-fe!1443
2022-03-10 09:14:32 +00:00
Pleroma Renovate Bot
00a5dfc36f Pin dependencies 2022-03-10 09:06:57 +00:00
HJ
e29f33ed0c Merge branch 'renovate/font-awesome' into 'develop'
Pin dependencies

See merge request pleroma/pleroma-fe!1444
2022-03-09 19:35:02 +00:00
Pleroma Renovate Bot
6946d09f55 Pin dependencies 2022-03-09 18:43:25 +00:00
Alexander Tumin
450145dd6b Do not mute bot posts in notifications 2022-03-09 07:56:43 +03:00
Tusooa Zhu
551b8f3690
Fix "max depth in thread" setting 2022-03-07 20:02:53 -05:00
Tusooa Zhu
48178bdc53
Make maxDepthInThread instance-default 2022-03-07 19:30:24 -05:00
Tusooa Zhu
a511250b63
Make $status-margin fallback only 2022-03-07 19:28:38 -05:00
Tusooa Zhu
415a823af0
Clean up dead code 2022-03-07 19:19:32 -05:00
Tusooa Zhu
0e323ef248
Add English translations for fading ancestor option 2022-03-07 19:19:32 -05:00
Tusooa Zhu
d157f67381
Make fading ancestors optional 2022-03-07 19:19:32 -05:00
Tusooa Zhu
fa20ea76b1
Update English translation for tree view settings 2022-03-07 19:19:32 -05:00
Tusooa Zhu
e7f5033c7d
Split conversation display style into two different settings
linear => linear (now default)
simple_tree => tree / conversationTreeAdvanced=false
tree => tree / conversationTreeAdvanced=true
2022-03-07 19:19:32 -05:00
Tusooa Zhu
9432fcec7d
Make 'Show full conversation' button have left border in embbeded mode 2022-03-07 19:19:32 -05:00
Tusooa Zhu
5768806d1b
Fix showingLongSubject not correctly propagated 2022-03-07 19:19:32 -05:00
Tusooa Zhu
f8c5cbcd0d
Fix timeline jump when scrolling
Ref: tree-threading
2022-03-07 19:19:31 -05:00
Tusooa Zhu
20880cdf0b
Make replying and mediaPlaying controlled
$refs is not a reliable way to deal with child components under
tree threading as it is not reactive, but the children may change at
any time. The only good way seems to be making these states aggregated on
the conversation component.

Ref: tree-threading
2022-03-07 19:19:31 -05:00
Tusooa Zhu
cc5cff2038
Clean up debug code for tree threading 2022-03-07 19:19:31 -05:00
Tusooa Zhu
2a510205c3
Fix virtual scrolling for tree threading
Ref: tree-threading
2022-03-07 19:19:31 -05:00
Tusooa Zhu
0db5a5a581
Fix controlled status display toggles 2022-03-07 19:19:31 -05:00
Tusooa Zhu
ba858a894c
Add English translations for other replies count 2022-03-07 19:19:31 -05:00
Tusooa Zhu
654996fdbe
Add other replies count for reply list link 2022-03-07 19:19:31 -05:00
Tusooa Zhu
cebb4224ac
Do not display replies inside status as link if there are no other replies 2022-03-07 19:19:31 -05:00
Tusooa Zhu
9b27ac9aaf
Add English translation for position of other replies button pref 2022-03-07 19:19:31 -05:00
Tusooa Zhu
863255d52f
Make position of other replies button a pref 2022-03-07 19:19:31 -05:00
Tusooa Zhu
c4bd004cbc
Add English translation for show all conversation button improvement 2022-03-07 19:19:31 -05:00
Tusooa Zhu
244174a32b
Improve "show full conversation" interaction
Now we only show that button when there are other statuses out of sight
(other toplevel statuses exist outside of the current thread tree).
2022-03-07 19:19:31 -05:00
Tusooa Zhu
22bdcda9c0
Make other replies button stretch along the row 2022-03-07 19:19:31 -05:00
Tusooa Zhu
ba8598858b
Optimise thread ancestor borders 2022-03-07 19:19:31 -05:00
Tusooa Zhu
17863f54fe
Optimise thread ancestor display style 2022-03-07 19:19:31 -05:00
Tusooa Zhu
26670e9003
Reset thread open state when collapsed 2022-03-07 19:19:30 -05:00
Tusooa Zhu
10cd03c718
Clean up 2022-03-07 19:19:30 -05:00
Tusooa Zhu
f1db5e8f4b
Highlight ancestor of the current status when diving back to top 2022-03-07 19:19:30 -05:00
Tusooa Zhu
d78c8e8ea4
Add English translation for Misskey-style tree view 2022-03-07 19:19:30 -05:00
Tusooa Zhu
e560fbc935
Implement Misskey-style tree view
Now the tree will be always rooted at the highlighted status, and
all its ancestors shown linearly on the top.

Enhancement: If an ancestor has more
than one reply (i.e. it has a child that is not on current status's
ancestor chain), we are given a link to root the thread at that status.
2022-03-07 19:19:30 -05:00
Tusooa Zhu
4adffb4835
Remove horizontal border and thicken vertical border in a thread tree 2022-03-07 19:19:30 -05:00
Tusooa Zhu
0e4a7c3d05
Make dive/undive button clickable along the whole row 2022-03-07 19:19:30 -05:00
Tusooa Zhu
8780246ce5
Optimize thread border radius 2022-03-07 19:19:30 -05:00
Tusooa Zhu
61bb69c88f
Optimize thread display 2022-03-07 19:19:30 -05:00
Tusooa Zhu
bdf631c2c4
Fix the bug where toggleShowingTall does not work 2022-03-07 19:19:30 -05:00
Tusooa Zhu
3addc36c96
Fix status undefined in parentOf 2022-03-07 19:19:30 -05:00
Tusooa Zhu
f851bc92c9
Add English translations for max depth in thread 2022-03-07 19:19:30 -05:00
Tusooa Zhu
2e54cf12c7
Add settings for max depth in thread 2022-03-07 19:19:30 -05:00
Tusooa Zhu
ff5f69b8fd
Use mergedConfig properly 2022-03-07 19:19:30 -05:00
Tusooa Zhu
d7da9f80a7
Fallback to simpleTree style 2022-03-07 19:19:30 -05:00
Tusooa Zhu
6b990ba368
Undive when collapsed 2022-03-07 19:19:29 -05:00
Tusooa Zhu
cd3e6d0073
Clean up 2022-03-07 19:19:29 -05:00
Tusooa Zhu
0aaef50ee5
Lint 2022-03-07 19:19:29 -05:00
Tusooa Zhu
05b2351e08
Add English translation for simple tree 2022-03-07 19:19:29 -05:00
Tusooa Zhu
d9a9f97751
Add simple tree style navigation 2022-03-07 19:19:29 -05:00
Tusooa Zhu
8c0deb905e
Add English translation for diving 2022-03-07 19:19:29 -05:00
Tusooa Zhu
d15d24c11c
Add dive functionality 2022-03-07 19:19:29 -05:00
Tusooa Zhu
31c4300456
Add English translations for diving 2022-03-07 19:19:29 -05:00
Tusooa Zhu
84a3cd92a3
Support diving into one status in a conversation 2022-03-07 19:19:29 -05:00
Tusooa Zhu
ace1f5067c
Make status display controlled 2022-03-07 19:19:29 -05:00
Tusooa Zhu
cd0f6a4f78
Add English translations for message threading 2022-03-07 19:19:29 -05:00
Tusooa Zhu
414ee55957
Make show full thread message account for numbers 2022-03-07 19:19:29 -05:00
Tusooa Zhu
0f2fd8a352
Implement thread folding/expanding 2022-03-07 19:19:29 -05:00
Tusooa Zhu
0582f19e7c
Add tree-style thread display 2022-03-07 19:19:29 -05:00
HJ
7e1e8ea429 Merge branch 'from/develop/tusooa/fix-lint-2' into 'develop'
Lint

See merge request pleroma/pleroma-fe!1442
2022-03-06 19:35:05 +00:00
Tusooa Zhu
7a8c975f61
Lint 2022-03-06 14:22:59 -05:00
HJ
1e41c2b99e Merge branch 'renovate/configure' into 'develop'
Configure Renovate

See merge request pleroma/pleroma-fe!1441
2022-03-06 18:25:05 +00:00
Pleroma Renovate Bot
f0f1066bc8 Add renovate.json 2022-03-06 17:50:32 +00:00
Alexander Tumin
2b7f12613e Add bot indication to user icon on statuses 2022-03-01 01:00:38 +03:00
Alexander Tumin
fe0ed7e8f0 Mute bot posts 2022-02-28 23:14:45 +03:00
HJ
514d7d3f88 Merge branch 'fix-hidden-attachments' into 'develop'
fix placeholder attachments opening new tab

Closes #1139

See merge request pleroma/pleroma-fe!1439
2022-02-28 17:46:58 +00:00
Henry Jameson
77b55a559b fix placeholder attachments opening new tab 2022-02-28 19:42:02 +02:00
Henry Jameson
77bb0b5530 lint 2022-02-28 18:23:32 +02:00
Henry Jameson
39909c8a85 pre-emptively wipe serverside settings on logout 2022-02-28 18:17:13 +02:00
Henry Jameson
f4b36a9ebf fix errors in choicesetting 2022-02-28 18:15:21 +02:00
Henry Jameson
67319d0e5b fix typos in profile page 2022-02-28 18:15:07 +02:00
Henry Jameson
cf58df17f6 hidden away more settings when logged out 2022-02-28 18:04:13 +02:00
Henry Jameson
8bb97fbfeb fix settings behaving erratically and not updating properly 2022-02-28 18:01:41 +02:00
Henry Jameson
3a5ad18aca fix stripping rich content not working 2022-02-28 18:00:38 +02:00
Henry Jameson
e3d602fdcc revert changes related to streaming/firehose setting, reword it so it's
not confused with websocket streaming
2022-02-28 17:45:07 +02:00
Henry Jameson
c07c0b2260 fix firefox rendering (??????????) 2022-02-28 17:43:08 +02:00
Henry Jameson
f626da838a revert to using local setting for default nsfw since backend is broken 2022-02-24 15:00:08 +02:00
Henry Jameson
9623b0e140 better phrasing 2022-02-24 14:41:55 +02:00
Henry Jameson
b1b9260a1d new defaults 2022-02-22 23:32:12 +02:00
Henry Jameson
9c1814d122 expert settings toggle + server-side settings 2022-02-22 23:31:40 +02:00
HJ
0300db6c63 Merge branch 'from/develop/tusooa/media-modal-counter-i18n' into 'develop'
Make media modal counter go through i18n

See merge request pleroma/pleroma-fe!1436
2022-02-21 16:15:16 +00:00
Tusooa Zhu
c7690aecd0
Add English translation for media modal counter 2022-02-21 10:34:20 -05:00
Tusooa Zhu
1b32bb9c51
Make media modal counter go through i18n 2022-02-21 10:33:47 -05:00
HJ
9bc7d99e16 Merge branch 'hj-develop-patch-74637' into 'develop'
Update changelog

See merge request pleroma/pleroma-fe!1435
2022-02-21 14:23:11 +00:00
HJ
965bc5573f Update CHANGELOG.md 2022-02-21 14:17:28 +00:00
HJ
2559d03d7e Update CHANGELOG.md 2022-02-21 14:16:45 +00:00
HJ
58d0f9678b Merge branch 'fix-mentions-new-bugs' into 'develop'
Fix newfound bugs with rich mentions + user suggestions

See merge request pleroma/pleroma-fe!1430
2022-02-20 15:11:52 +00:00
HJ
ddee8bb686 Merge branch 'proper-attachments' into 'develop'
Attachment improvements

See merge request pleroma/pleroma-fe!1399
2022-02-20 15:11:33 +00:00
HJ
56616787ec Merge branch 'fix-pinned-statuses' into 'develop'
Fix pinned statuses appearing at the bottom of user timeline

Closes #1112

See merge request pleroma/pleroma-fe!1433
2022-02-20 15:11:08 +00:00
HJ
d3659b5597 Merge branch 'fix-1133' into 'develop'
fix #1133

Closes #1133

See merge request pleroma/pleroma-fe!1434
2022-02-20 14:12:20 +00:00
Henry Jameson
373c30be69 fix #1133 2022-02-20 16:06:26 +02:00
Henry Jameson
7b60adb480 only for user TL 2022-02-20 00:57:31 +02:00
Henry Jameson
5864dc52f7 removed file because that logic has been removed 2022-02-20 00:43:38 +02:00
Henry Jameson
a31ff20f50 lol, lmao, that was some shit, this is much easier and works all the time 2022-02-20 00:36:21 +02:00
Henry Jameson
2a97bdb39d fix pinned statuses appearing at the bottom of user timeline (and
possibly fetching new ones there)
2022-02-20 00:04:47 +02:00
Henry Jameson
86e3aefdab new unit tests 2022-02-19 23:04:51 +02:00
Henry Jameson
a8d1987686 fix unit tests 2022-02-19 22:40:19 +02:00
Henry Jameson
0d073d607c enable link handling in user bios to fix links not having _blank 2022-02-19 22:34:08 +02:00
Henry Jameson
769a9a14fe add emoji to chat titles 2022-02-19 22:30:13 +02:00
Henry Jameson
a61f6e1590 use rich content in interaction lists 2022-02-19 22:12:37 +02:00
Henry Jameson
ee86f56469 more spacing fixes 2022-02-11 15:10:52 +02:00
Henry Jameson
17d6f1b53b more spacing fixes 2022-02-11 15:06:12 +02:00
Henry Jameson
befd4d5fc7 improve unknown attachment handling 2022-02-10 15:42:28 +02:00
Henry Jameson
4c7edfc9a9 more spacing/wrapping fixes 2022-02-09 23:34:26 +02:00
Henry Jameson
d361a4d7dc fix overflows 2022-02-04 14:20:56 +02:00
Henry Jameson
571e73a346 better approach to unescaping 2022-02-03 23:13:28 +02:00
Henry Jameson
39ecb33883 fix amps in links 2022-02-03 22:58:12 +02:00
Henry Jameson
0f01931309 make chat messages behave same as posts for animated gifs 2022-02-03 22:52:41 +02:00
Henry Jameson
a9830ff491 support width/height img attributes 2022-02-03 22:50:32 +02:00
Henry Jameson
9a6363431d lint 2022-02-03 22:41:38 +02:00
Henry Jameson
ea6114f63c better phrasing? 2022-02-03 22:36:13 +02:00
Henry Jameson
bfb3a4364b options to disable (You)s and highlighting of yourself 2022-02-03 22:34:57 +02:00
Henry Jameson
6d3229b1a1 fix poast mentions tripping 2022-02-03 22:23:28 +02:00
Henry Jameson
06042569f1 fix alignment issues 2022-02-03 22:10:45 +02:00
HJ
8ade11783a Merge branch 'from/develop/tusooa/1118-enhanced-mention-link' into 'develop'
Enhanced mention link

Closes #1118

See merge request pleroma/pleroma-fe!1424
2022-02-03 19:37:13 +00:00
HJ
a5e20a4eb2 Merge branch 'from/develop/tusooa/fix-follow-list' into 'develop'
Fix Follow button missing on follow list

See merge request pleroma/pleroma-fe!1428
2022-02-03 10:22:22 +00:00
HJ
dd75e43c57 Merge branch 'from/develop/tusooa/fix-lint-warn' into 'develop'
Fix lint warning in filtering_tab.vue

See merge request pleroma/pleroma-fe!1429
2022-02-03 10:22:04 +00:00
Tusooa Zhu
0e6af68a0f
Fix lint warning in filtering_tab.vue 2022-02-02 21:35:17 -05:00
Tusooa Zhu
f886135bb7
Fix Follow button missing on follow list 2022-02-02 21:30:52 -05:00
Tusooa Zhu
c3f1765b21
Hide mention link avatar by default 2022-01-29 16:57:59 -05:00
Tusooa Zhu
7cc0d0763c
Add English translation for mention link prefs 2022-01-29 16:57:59 -05:00
Tusooa Zhu
c8983d5606
Make mention link prefs ui more intuitive 2022-01-29 16:57:59 -05:00
Tusooa Zhu
0c60b31eee
Add option to fade domains in mention link 2022-01-29 16:57:59 -05:00
Tusooa Zhu
aaf0b985ad
Make avatar unselectable 2022-01-29 16:57:59 -05:00
Tusooa Zhu
9fde13c968
Add option to display user avatar in mention link 2022-01-29 16:57:59 -05:00
Tusooa Zhu
8896afd8d8
Make (You) unselectable 2022-01-29 16:57:59 -05:00
Tusooa Zhu
95007059d1
Style properly usernames without tooltips 2022-01-29 16:57:59 -05:00
Tusooa Zhu
1d4b1b296e
Add pref for whether to display full user names and tooltips 2022-01-29 16:57:59 -05:00
Tusooa Zhu
76547fe66d
Add a pref for whether to display mention as icon or text 2022-01-29 16:57:59 -05:00
Henry Jameson
d1f02221cb changelog 2022-01-24 21:44:41 +02:00
Henry Jameson
efd558f394 Merge remote-tracking branch 'origin/develop' into proper-attachments
* origin/develop:
  Update CHANGELOG.md
2022-01-24 21:38:08 +02:00
HJ
28ee5721bb Merge branch 'hj-develop-patch-16931' into 'develop'
Update CHANGELOG.md

See merge request pleroma/pleroma-fe!1427
2022-01-24 19:31:42 +00:00
Henry Jameson
94c37e8adf Merge remote-tracking branch 'origin/develop' into proper-attachments
* origin/develop:
  undo accidental change when merging
  Apply 1 suggestion(s) to 1 file(s)
  Rearranged settings, moved more stuff to filtering where apllicable. Changed how filering works.
2022-01-24 21:29:19 +02:00
HJ
e5f731c97d Update CHANGELOG.md 2022-01-24 19:29:01 +00:00
HJ
3cf7f9e3c8 Merge branch 'settings-and-filtering' into 'develop'
Settings rearrange and filtering improvements

See merge request pleroma/pleroma-fe!1394
2022-01-24 18:18:27 +00:00
Henry Jameson
56de3d2f52 fix too-many-attachments in notifications column 2022-01-24 19:53:17 +02:00
Henry Jameson
c551e3e697 Merge remote-tracking branch 'origin/develop' into proper-attachments
* origin/develop: (81 commits)
  Improve the user card for deactivated users
  Update CHANGELOG.md
  Update CHANGELOG.md
  Allow canceling a follow request
  Simple policy reasons for instance specific policies
  entity_normalizer: Escape name when parsing user
  Translated using Weblate (Spanish)
  Translated using Weblate (Catalan)
  Translated using Weblate (Korean)
  Translated using Weblate (Japanese (ja_PEDANTIC))
  Translated using Weblate (Indonesian)
  Translated using Weblate (Esperanto)
  Translated using Weblate (Vietnamese)
  Translated using Weblate (Italian)
  Translated using Weblate (Vietnamese)
  Translated using Weblate (Indonesian)
  Translated using Weblate (Italian)
  Translated using Weblate (Vietnamese)
  Translated using Weblate (Indonesian)
  Translated using Weblate (Chinese (Simplified))
  ...
2022-01-24 19:28:38 +02:00
Henry Jameson
f7e2ac1c48 undo accidental change when merging 2022-01-24 19:16:03 +02:00
Henry Jameson
2f8aae371b Merge remote-tracking branch 'origin/settings-and-filtering' into settings-and-filtering
* origin/settings-and-filtering:
  Apply 1 suggestion(s) to 1 file(s)
2022-01-24 19:13:29 +02:00
Henry Jameson
9ea0f10abb Merge remote-tracking branch 'origin/develop' into settings-and-filtering
* origin/develop: (169 commits)
  Improve the user card for deactivated users
  Update CHANGELOG.md
  Update CHANGELOG.md
  Allow canceling a follow request
  Simple policy reasons for instance specific policies
  entity_normalizer: Escape name when parsing user
  Translated using Weblate (Spanish)
  Translated using Weblate (Catalan)
  Translated using Weblate (Korean)
  Translated using Weblate (Japanese (ja_PEDANTIC))
  Translated using Weblate (Indonesian)
  Translated using Weblate (Esperanto)
  Translated using Weblate (Vietnamese)
  Translated using Weblate (Italian)
  Translated using Weblate (Vietnamese)
  Translated using Weblate (Indonesian)
  Translated using Weblate (Italian)
  Translated using Weblate (Vietnamese)
  Translated using Weblate (Indonesian)
  Translated using Weblate (Chinese (Simplified))
  ...
2022-01-24 19:12:17 +02:00
HJ
182fcca5da Merge branch 'weblate-pleroma-pleroma-fe' into 'develop'
Translations update from Weblate

See merge request pleroma/pleroma-fe!1413
2022-01-19 13:10:16 +00:00
HJ
6509542dbd Merge branch 'fixes_for_deactivated_profile' into 'develop'
Improve the user card for deactivated users

See merge request pleroma/pleroma-fe!1417
2022-01-19 13:09:49 +00:00
Ilja
2ac78219ee Improve the user card for deactivated users 2022-01-19 13:09:48 +00:00
Shpuld Shpludson
756f7bf7c2 Merge branch 'shpuld-develop-patch-58245' into 'develop'
Update CHANGELOG.md

See merge request pleroma/pleroma-fe!1423
2022-01-09 18:29:46 +00:00
Shpuld Shpludson
4cd27acf7f Update CHANGELOG.md 2022-01-09 18:26:35 +00:00
Shpuld Shpludson
030c374def Merge branch 'shpuld-develop-patch-87791' into 'develop'
Update CHANGELOG.md

See merge request pleroma/pleroma-fe!1422
2022-01-09 18:21:14 +00:00
Shpuld Shpludson
056f5f547a Update CHANGELOG.md 2022-01-09 18:16:44 +00:00
HJ
af2a408b78 Apply 1 suggestion(s) to 1 file(s) 2022-01-09 17:59:50 +00:00
HJ
d22e04eaf6 Merge branch 'allow_to_cancel_follow_request' into 'develop'
Allow canceling a follow request

See merge request pleroma/pleroma-fe!1416
2021-12-28 11:43:24 +00:00
Ilja
4587f37dd7 Allow canceling a follow request
When a follow request is sent, but not (yet) accepted, the behaviour is now to cancel the request instead of re sending.

The reason is double
* You couldn't cancel a follow request if you change your mind and the request wasn't answered yet
* Instances don't always correctly process a new follow request when the following is already happening. If something went wrong (e;g. the target server thinks you're following, but your instance thinks you're not yet), it's better to first sent an unfollow. This is the behaviour that Mastodon and most probably most other clients have. Therefore this flow is more tested and expected by other instances.
2021-12-12 18:09:21 +01:00
tarteka
d2730d5bad Translated using Weblate (Spanish)
Currently translated at 100.0% (722 of 722 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/es/
2021-10-21 19:22:22 +00:00
retiolus
1c75c74c91 Translated using Weblate (Catalan)
Currently translated at 99.7% (720 of 722 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ca/
2021-09-29 00:45:54 +00:00
Ryo Ueno
ef684dff61 Translated using Weblate (Korean)
Currently translated at 61.9% (447 of 722 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ko/
2021-09-19 09:45:49 +00:00
Ryo Ueno
241b4957e1 Translated using Weblate (Japanese (ja_PEDANTIC))
Currently translated at 99.4% (718 of 722 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/ja_PEDANTIC/
2021-09-19 09:45:49 +00:00
@liimee
45eda03d1c Translated using Weblate (Indonesian)
Currently translated at 68.1% (492 of 722 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/id/
2021-09-16 00:45:47 +00:00
Tirifto
1d0e4bada8 Translated using Weblate (Esperanto)
Currently translated at 100.0% (722 of 722 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/eo/
2021-09-14 15:45:47 +00:00
Hồ Nhất Duy
3c8ced53b9 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (722 of 722 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/vi/
2021-09-09 22:00:26 +00:00
Ben Is
80dd6b2500 Translated using Weblate (Italian)
Currently translated at 100.0% (722 of 722 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2021-09-09 22:00:26 +00:00
Hồ Nhất Duy
ad64b91d66 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (716 of 716 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/vi/
2021-09-09 22:00:26 +00:00
@liimee
4988268f5f Translated using Weblate (Indonesian)
Currently translated at 67.5% (484 of 716 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/id/
2021-09-09 22:00:26 +00:00
Ben Is
50adf0ddf2 Translated using Weblate (Italian)
Currently translated at 100.0% (716 of 716 strings)

Translation: Pleroma/Pleroma-FE
Translate-URL: https://translate.pleroma.social/projects/pleroma/pleroma-fe/it/
2021-09-09 22:00:26 +00:00
Henry Jameson
49fe334186 play gifs when hovering over notification 2021-08-16 01:34:35 +03:00
Henry Jameson
14ec7d6a41 add attachment counter 2021-08-16 01:11:43 +03:00
Henry Jameson
7cc19ef2ea better media modal loading 2021-08-16 01:11:42 +03:00
Henry Jameson
0507eb6550 ability to move attachments around when making a new post 2021-08-15 21:04:49 +03:00
Henry Jameson
830a03a0d1 inline description display 2021-08-15 21:04:28 +03:00
Henry Jameson
34d265467a add media description into media modal 2021-08-15 19:45:48 +03:00
Henry Jameson
f5823a96e9 add key attribute to make image refresh in media modal to give feedback
when images are still loaded
2021-08-15 19:43:52 +03:00
Henry Jameson
777f6c0162 Merge branch 'better-still-emoji' into proper-attachments
* better-still-emoji:
  fix "+X more" sticking
2021-08-15 18:43:52 +03:00
Henry Jameson
299c00cf74 fix video attachments in notifications not having pointer cursor 2021-08-15 18:35:26 +03:00
Henry Jameson
07c12ae162 replace poll with an icon in notifications 2021-08-15 18:26:37 +03:00
Henry Jameson
c8a7b6f433 fix long posts double-fading in notifications 2021-08-15 18:21:25 +03:00
Henry Jameson
5431d8fe55 Merge branch 'better-still-emoji' into proper-attachments
* better-still-emoji:
  fix links sticking to mentionsline
2021-08-15 18:11:57 +03:00
Henry Jameson
6aa6f6f300 fix console errors 2021-08-15 17:53:35 +03:00
Henry Jameson
17d2eed06a Merge branch 'better-still-emoji' into proper-attachments
* better-still-emoji:
  fix tests
  prevent infinite update loops
  remove obsolete tests
  removed useless code, review change, fixed bug with tall statuses
  fixed mentions line again
  remove old emoji added, everything emoji-bearing uses RichContent now
  richcontent support in polls, user cards and user profiles
  support richcontent in polls
  fix tests, add performance test (skipped, doesn't assert anything), tweak max mentions count
  made the code responsible for showing unwritten mentions actually work
  remove new options for style and separate line, now groups all chained mentions on a mentionsline regardless of placement. fixes spacing
  fix tests
2021-08-15 16:27:41 +03:00
Henry Jameson
b67db47c88 lint 2021-06-22 20:47:35 +03:00
Henry Jameson
dd3fe61cf3 Merge branch 'better-still-emoji' into proper-attachments
* better-still-emoji:
  fix non-notifying mentions and original mention display
  fix not escaping some stuff
  fix rich images
2021-06-22 20:45:44 +03:00
Henry Jameson
a2f21f4e13 fix description colliding with extra-long text 2021-06-22 20:42:52 +03:00
Henry Jameson
628b99d117 don't stretch columns when uploading media 2021-06-22 20:37:08 +03:00
Henry Jameson
5118eee19a fix videos not stretching to container 2021-06-22 20:35:34 +03:00
Henry Jameson
4ba8d95a10 fix videos and related not having working drag controls 2021-06-22 20:33:57 +03:00
Henry Jameson
4016182b89 fix z-indexes 2021-06-22 20:32:55 +03:00
Henry Jameson
6b8b9c017f whoops 2021-06-18 17:39:29 +03:00
Henry Jameson
44b741e270 better attachments in uploading (grid layout) 2021-06-18 17:30:56 +03:00
Henry Jameson
8bab8658e8 better handling of unknown files, better upload display 2021-06-18 16:11:16 +03:00
Henry Jameson
bfe31e20ea better compact attachments 2021-06-18 14:12:50 +03:00
Henry Jameson
5c2744b426 Merge branch 'better-still-emoji' into proper-attachments
* better-still-emoji:
  Use proper setting name
  Use cleaner instance config check for shoutbox setting
  Make locale language cleaner
  Don't shorten shoutbox to SB
  Fix lint error
  Update CHANGELOG.md
  New option: Hide shoutbox
2021-06-18 02:28:11 +03:00
Henry Jameson
f35c090caa merged in compact notifs and improved upon it 2021-06-18 02:27:32 +03:00
Henry Jameson
c1293c3afa Merge branch 'compact-notifs' into proper-attachments
* compact-notifs:
  compact notifs
2021-06-18 02:09:50 +03:00
Henry Jameson
f15599e6e5 gallery in post status form! 2021-06-18 02:04:01 +03:00
Henry Jameson
90345f158f gallery now supports flash, fixes for flash component. refactored media modal 2021-06-18 02:03:38 +03:00
Henry Jameson
e654fead23 refactored attachments and gallery. All attachments now are in gallery. 2021-06-17 16:29:46 +03:00
Eris
9c4957268d Use proper setting name 2021-06-17 13:21:25 +03:00
Eris
6689fed513 Use cleaner instance config check for shoutbox setting 2021-06-17 13:21:25 +03:00
Eris
4ecbb58086 Make locale language cleaner 2021-06-17 13:21:25 +03:00
Eris
0a3ce9cc8b Don't shorten shoutbox to SB 2021-06-17 13:21:25 +03:00
Eris
dcfd178314 Fix lint error 2021-06-17 13:21:25 +03:00
Eris
1b26c713ef Update CHANGELOG.md 2021-06-17 13:21:25 +03:00
Eris
9e9ab5cec9 New option: Hide shoutbox 2021-06-17 13:21:25 +03:00
Henry Jameson
a96a62929d Merge remote-tracking branch 'origin/develop' into settings-and-filtering
* origin/develop:
  Use proper setting name
  Use cleaner instance config check for shoutbox setting
  Make locale language cleaner
  Don't shorten shoutbox to SB
  Fix lint error
  Update CHANGELOG.md
  New option: Hide shoutbox
2021-06-16 13:52:13 +03:00
Henry Jameson
a3c703bd37 compact notifs 2021-06-14 02:52:41 +03:00
Henry Jameson
b87a9d6675 Rearranged settings, moved more stuff to filtering where apllicable.
Changed how filering works.
2021-06-08 16:14:01 +03:00
Henry Jameson
8a9115b58e temp fix for now-unused resettable async component 2021-04-25 14:51:15 +03:00
Henry Jameson
b6e8c12dbc emoji picker fix 2021-04-25 14:51:00 +03:00
Henry Jameson
e73cb423b6 fix login form 2021-04-25 14:44:07 +03:00
Henry Jameson
e47d5ba53b fix importer/exporter i18n 2021-04-25 14:12:34 +03:00
Henry Jameson
8d46fd78c7 migrate to v-slot 2021-04-25 14:05:25 +03:00
Henry Jameson
95e74319e1 clean warnings from status 2021-04-25 14:05:25 +03:00
Henry Jameson
709b75198d fix portals/teleports 2021-04-25 14:05:25 +03:00
Henry Jameson
caed89f0ae destroyed -> unmounted 2021-04-25 13:44:50 +03:00
Henry Jameson
72956e2343 fix HOCs 2021-04-25 13:40:08 +03:00
Henry Jameson
4b18e0f36e fix status error 2021-04-25 13:33:02 +03:00
Henry Jameson
b479d80366 fix i18n in services 2021-04-25 13:30:18 +03:00
Henry Jameson
9e8513b312 i18n fixes 2021-04-25 13:25:42 +03:00
Henry Jameson
52835cf8bf work around modules cyclic dependencies 2021-04-25 13:25:04 +03:00
Henry Jameson
905b9771ec stop using vue.set 2021-04-25 13:24:08 +03:00
Henry Jameson
fca885e665 resolve TODO VUE3 2021-04-25 13:23:16 +03:00
Henry Jameson
6e687c0663 fix one async component preventing further load 2021-04-25 13:15:02 +03:00
Henry Jameson
180da297f6 Merge branch 'vue3compat-tabswitcher' into vue3-again
* vue3compat-tabswitcher:
  small refactoring to uncouple tab-switcher from settings modal
  fix theme tab, remove console.logs
  Changed some of TabSwitcher's internals for easier Vue3 migration
2021-04-25 12:51:45 +03:00
Henry Jameson
b774472fff Merge branch 'vue3compat-emoji-input' into vue3-again
* vue3compat-emoji-input:
  backport vue3 changes related to emoji-input
2021-04-25 12:51:21 +03:00
Henry Jameson
76a2e6befb remove Vue.component from hooks 2021-04-25 12:50:17 +03:00
Henry Jameson
1f5f612163 remove Vue.component, just export an object. Seems to be working 2021-04-25 12:47:52 +03:00
Henry Jameson
509ec99574 some minor fixes to get it to boot 2021-04-24 18:04:35 +03:00
Henry Jameson
ced9c0fa7e some bare minimum to get vue3 boot (no UI yet) 2021-04-24 17:56:00 +03:00
Henry Jameson
33777fab47 small refactoring to uncouple tab-switcher from settings modal 2021-04-18 15:39:06 +03:00
Henry Jameson
b0789fd6fd fix theme tab, remove console.logs 2021-04-18 15:03:28 +03:00
Henry Jameson
433ea02a18 Changed some of TabSwitcher's internals for easier Vue3 migration 2021-04-18 14:58:02 +03:00
208 changed files with 8184 additions and 4034 deletions

View file

@ -1,5 +1,5 @@
{ {
"presets": ["@babel/preset-env", "@vue/babel-preset-jsx"], "presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime", "lodash"], "plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-jsx"],
"comments": false "comments": false
} }

View file

@ -1,7 +1,7 @@
# This file is a template, and might need editing before it works on your project. # This file is a template, and might need editing before it works on your project.
# Official framework image. Look for the different tagged releases at: # Official framework image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/node/tags/ # https://hub.docker.com/r/library/node/tags/
image: node:10 image: node:12
stages: stages:
- lint - lint

View file

@ -3,6 +3,56 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Fixed
- AdminFE button no longer scrolls page to top when clicked
- Pinned statuses no longer appear at bottom of user timeline (still appear as part of the timeline when fetched deep enough)
- Fixed many many bugs related to new mentions, including spacing and alignment issues
- Links in profile bios now properly open in new tabs
- Inline images now respect their intended width/height attributes
- Links with `&` in them work properly now
- Interaction list popovers now properly emojify names
- Completely hidden posts still had 1px border
- Attachments are ALWAYS in same order as user uploaded, no more "videos first"
- Attachment description is prefilled with backend-provided default when uploading
- Proper visual feedback that next image is loading when browsing
### Changed
- (You)s are optional (opt-in) now, bolding your nickname is also optional (opt-out)
- User highlight background now also covers the `@`
- Reverted back to textual `@`, svg version is opt-in.
- Settings window has been throughly rearranged to make make more sense and make navication settings easier.
- Uploaded attachments are uniform with displayed attachments
- Flash is watchable in media-modal (takes up nearly full screen though due to sizing issues)
- Notifications about likes/repeats/emoji reacts are now minimized so they always take up same amount of space irrelevant to size of post.
### Added
- Options to show domains in mentions
- Option to show user avatars in mention links (opt-in)
- Option to disable the tooltip for mentions
- Option to completely hide muted threads
- Ability to open videos in modal even if you disabled that feature, via an icon button
- New button on attachment that indicates that attachment has a description and shows a bar filled with description
- Attachments are truncated just like post contents
- Media modal now also displays description and counter position in gallery (i.e. 1/5)
- Ability to rearrange order of attachments when uploading
- Enabled users to zoom and pan images in media viewer with mouse and touch
## [2.4.2] - 2022-01-09
### Added
- Added Apply and Reset buttons to the bottom of theme tab to minimize UI travel
- Implemented user option to always show floating New Post button (normally mobile-only)
- Display reasons for instance specific policies
- Added functionality to cancel follow request
### Fixed
- Fixed link to external profile not working on user profiles
- Fixed mobile shoutbox display
- Fixed favicon badge not working in Chrome
- Escape html more properly in subject/display name
## [2.4.0] - 2021-08-08 ## [2.4.0] - 2021-08-08
### Added ### Added
- Added a quick settings to timeline header for easier access - Added a quick settings to timeline header for easier access
@ -11,12 +61,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Implemented user option to change sidebar position to the right side - Implemented user option to change sidebar position to the right side
- Implemented user option to hide floating shout panel - Implemented user option to hide floating shout panel
- Implemented "edit profile" button if viewing own profile which opens profile settings - Implemented "edit profile" button if viewing own profile which opens profile settings
- Added Apply and Reset buttons to the bottom of theme tab to minimize UI travel
- Implemented user option to always show floating New Post button (normally mobile-only)
### Fixed ### Fixed
- Fixed follow request count showing in the wrong location in mobile view - Fixed follow request count showing in the wrong location in mobile view
## [2.3.0] - 2021-03-01 ## [2.3.0] - 2021-03-01
### Fixed ### Fixed
- Button to remove uploaded media in post status form is now properly placed and sized. - Button to remove uploaded media in post status form is now properly placed and sized.

View file

@ -4,6 +4,7 @@ var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../') var projectRoot = path.resolve(__dirname, '../')
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin') var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
var CopyPlugin = require('copy-webpack-plugin'); var CopyPlugin = require('copy-webpack-plugin');
var { VueLoaderPlugin } = require('vue-loader')
var env = process.env.NODE_ENV var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the // check env & config/index.js to decide weither to enable CSS Sourcemaps for the
@ -29,16 +30,16 @@ module.exports = {
} }
}, },
resolve: { resolve: {
extensions: ['.js', '.vue'], extensions: ['.js', '.jsx', '.vue'],
modules: [ modules: [
path.join(__dirname, '../node_modules') path.join(__dirname, '../node_modules')
], ],
alias: { alias: {
'vue$': 'vue/dist/vue.runtime.common',
'static': path.resolve(__dirname, '../static'), 'static': path.resolve(__dirname, '../static'),
'src': path.resolve(__dirname, '../src'), 'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'), 'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components') 'components': path.resolve(__dirname, '../src/components'),
'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js'
} }
}, },
module: { module: {
@ -58,9 +59,28 @@ module.exports = {
} }
} }
}, },
{
enforce: 'post',
test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
type: 'javascript/auto',
loader: '@intlify/vue-i18n-loader',
include: [ // Use `Rule.include` to specify the files of locale messages to be pre-compiled
path.resolve(__dirname, '../src/i18n')
]
},
{ {
test: /\.vue$/, test: /\.vue$/,
use: 'vue-loader' loader: 'vue-loader',
options: {
compilerOptions: {
isCustomElement(tag) {
if (tag === 'pinch-zoom') {
return true
}
return false
}
}
}
}, },
{ {
test: /\.jsx?$/, test: /\.jsx?$/,
@ -95,6 +115,7 @@ module.exports = {
entry: path.join(__dirname, '..', 'src/sw.js'), entry: path.join(__dirname, '..', 'src/sw.js'),
filename: 'sw-pleroma.js' filename: 'sw-pleroma.js'
}), }),
new VueLoaderPlugin(),
// This copies Ruffle's WASM to a directory so that JS side can access it // This copies Ruffle's WASM to a directory so that JS side can access it
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [

View file

@ -21,7 +21,9 @@ module.exports = merge(baseWebpackConfig, {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': config.dev.env, 'process.env': config.dev.env,
'COMMIT_HASH': JSON.stringify('DEV'), 'COMMIT_HASH': JSON.stringify('DEV'),
'DEV_OVERRIDES': JSON.stringify(config.dev.settings) 'DEV_OVERRIDES': JSON.stringify(config.dev.settings),
'__VUE_OPTIONS_API__': true,
'__VUE_PROD_DEVTOOLS__': false
}), }),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),

View file

@ -36,7 +36,9 @@ var webpackConfig = merge(baseWebpackConfig, {
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': env, 'process.env': env,
'COMMIT_HASH': JSON.stringify(commitHash), 'COMMIT_HASH': JSON.stringify(commitHash),
'DEV_OVERRIDES': JSON.stringify(undefined) 'DEV_OVERRIDES': JSON.stringify(undefined),
'__VUE_OPTIONS_API__': true,
'__VUE_PROD_DEVTOOLS__': false
}), }),
// extract css into its own file // extract css into its own file
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({

View file

@ -52,7 +52,10 @@ module.exports = {
target, target,
changeOrigin: true, changeOrigin: true,
cookieDomainRewrite: 'localhost', cookieDomainRewrite: 'localhost',
ws: true ws: true,
headers: {
'Origin': target
}
}, },
'/oauth/revoke': { '/oauth/revoke': {
target, target,

View file

@ -16,107 +16,111 @@
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.7.6", "@babel/runtime": "7.17.8",
"@chenfengyuan/vue-qrcode": "^1.0.0", "@chenfengyuan/vue-qrcode": "2.0.0",
"@fortawesome/fontawesome-svg-core": "^1.2.32", "@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-regular-svg-icons": "^5.15.1", "@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "^2.0.0", "@fortawesome/vue-fontawesome": "3.0.0-5",
"body-scroll-lock": "^2.6.4", "@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"chromatism": "^3.0.0", "@vuelidate/core": "2.0.0-alpha.35",
"cropperjs": "^1.4.3", "@vuelidate/validators": "2.0.0-alpha.27",
"diff": "^3.0.1", "body-scroll-lock": "2.7.1",
"escape-html": "^1.0.3", "chromatism": "3.0.0",
"localforage": "^1.5.0", "click-outside-vue3": "4.0.1",
"parse-link-header": "^1.0.1", "cropperjs": "1.5.12",
"phoenix": "^1.3.0", "diff": "3.5.0",
"portal-vue": "^2.1.4", "escape-html": "1.0.3",
"punycode.js": "^2.1.0", "localforage": "1.10.0",
"ruffle-mirror": "^2021.4.10", "parse-link-header": "1.0.1",
"v-click-outside": "^2.1.1", "phoenix": "1.6.2",
"vue": "^2.6.11", "punycode.js": "2.1.0",
"vue-i18n": "^7.3.2", "qrcode": "1",
"vue-router": "^3.0.1", "ruffle-mirror": "2021.12.31",
"vue-template-compiler": "^2.6.11", "vue": "^3.2.31",
"vuelidate": "^0.7.4", "vue-i18n": "^9.2.0-beta.34",
"vuex": "^3.0.1" "vue-router": "4.0.14",
"vue-template-compiler": "2.6.11",
"vuex": "4.0.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.7.5", "@babel/core": "7.17.8",
"@babel/plugin-transform-runtime": "^7.7.6", "@babel/plugin-transform-runtime": "7.17.0",
"@babel/preset-env": "^7.7.6", "@babel/preset-env": "7.16.11",
"@babel/register": "^7.7.4", "@babel/register": "7.17.7",
"@ungap/event-target": "^0.1.0", "@intlify/vue-i18n-loader": "^5.0.0",
"@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", "@ungap/event-target": "0.2.3",
"@vue/babel-preset-jsx": "^1.2.4", "@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
"@vue/test-utils": "^1.0.0-beta.26", "@vue/babel-plugin-jsx": "1.1.1",
"autoprefixer": "^6.4.0", "@vue/compiler-sfc": "^3.1.0",
"babel-eslint": "^7.0.0", "@vue/test-utils": "2.0.0-rc.17",
"babel-loader": "^8.0.6", "autoprefixer": "6.7.7",
"babel-plugin-lodash": "^3.3.4", "babel-eslint": "7.2.3",
"chai": "^3.5.0", "babel-loader": "8.2.4",
"chalk": "^1.1.3", "babel-plugin-lodash": "3.3.4",
"chromedriver": "^87.0.1", "chai": "3.5.0",
"connect-history-api-fallback": "^1.1.0", "chalk": "1.1.3",
"copy-webpack-plugin": "^6.4.1", "chromedriver": "87.0.7",
"cross-spawn": "^4.0.2", "connect-history-api-fallback": "1.6.0",
"css-loader": "^0.28.0", "copy-webpack-plugin": "6.4.1",
"custom-event-polyfill": "^1.0.7", "cross-spawn": "4.0.2",
"eslint": "^5.16.0", "css-loader": "0.28.11",
"eslint-config-standard": "^12.0.0", "custom-event-polyfill": "1.0.7",
"eslint-friendly-formatter": "^2.0.5", "eslint": "5.16.0",
"eslint-loader": "^2.1.0", "eslint-config-standard": "12.0.0",
"eslint-plugin-import": "^2.13.0", "eslint-friendly-formatter": "2.0.7",
"eslint-plugin-node": "^7.0.0", "eslint-loader": "2.2.1",
"eslint-plugin-promise": "^4.0.0", "eslint-plugin-import": "2.25.4",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-node": "7.0.1",
"eslint-plugin-vue": "^5.2.2", "eslint-plugin-promise": "4.3.1",
"eventsource-polyfill": "^0.9.6", "eslint-plugin-standard": "4.1.0",
"express": "^4.13.3", "eslint-plugin-vue": "5.2.3",
"file-loader": "^3.0.1", "eventsource-polyfill": "0.9.6",
"function-bind": "^1.0.2", "express": "4.17.3",
"html-webpack-plugin": "^3.0.0", "file-loader": "3.0.1",
"http-proxy-middleware": "^0.17.2", "function-bind": "1.1.1",
"inject-loader": "^2.0.1", "html-webpack-plugin": "3.2.0",
"iso-639-1": "^2.0.3", "http-proxy-middleware": "0.21.0",
"isparta-loader": "^2.0.0", "inject-loader": "2.0.1",
"json-loader": "^0.5.4", "iso-639-1": "2.1.13",
"karma": "^3.0.0", "isparta-loader": "2.0.0",
"karma-coverage": "^1.1.1", "json-loader": "0.5.7",
"karma-firefox-launcher": "^1.1.0", "karma": "6.3.17",
"karma-mocha": "^1.2.0", "karma-coverage": "1.1.2",
"karma-mocha-reporter": "^2.2.1", "karma-firefox-launcher": "1.3.0",
"karma-sinon-chai": "^2.0.2", "karma-mocha": "2.0.1",
"karma-sourcemap-loader": "^0.3.7", "karma-mocha-reporter": "2.2.5",
"karma-spec-reporter": "0.0.26", "karma-sinon-chai": "2.0.2",
"karma-webpack": "^4.0.0-rc.3", "karma-sourcemap-loader": "0.3.8",
"lodash": "^4.16.4", "karma-spec-reporter": "0.0.33",
"lolex": "^1.4.0", "karma-webpack": "4.0.2",
"mini-css-extract-plugin": "^0.5.0", "lodash": "4.17.21",
"mocha": "^3.1.0", "lolex": "1.6.0",
"nightwatch": "^0.9.8", "mini-css-extract-plugin": "0.12.0",
"opn": "^4.0.2", "mocha": "3.5.3",
"ora": "^0.3.0", "nightwatch": "0.9.21",
"postcss-loader": "^3.0.0", "opn": "4.0.2",
"raw-loader": "^0.5.1", "ora": "0.4.1",
"sass": "^1.17.3", "postcss-loader": "3.0.0",
"sass-loader": "git://github.com/webpack-contrib/sass-loader", "raw-loader": "0.5.1",
"sass": "1.20.1",
"sass-loader": "7.2.0",
"selenium-server": "2.53.1", "selenium-server": "2.53.1",
"semver": "^5.3.0", "semver": "5.6.0",
"serviceworker-webpack-plugin": "^1.0.0", "serviceworker-webpack-plugin": "1.0.1",
"shelljs": "^0.8.4", "shelljs": "0.8.5",
"sinon": "^2.1.0", "sinon": "2.4.1",
"sinon-chai": "^2.8.0", "sinon-chai": "2.14.0",
"stylelint": "^13.6.1", "stylelint": "13.6.1",
"stylelint-config-standard": "^20.0.0", "stylelint-config-standard": "20.0.0",
"stylelint-rscss": "^0.4.0", "stylelint-rscss": "0.4.0",
"url-loader": "^1.1.2", "url-loader": "1.1.2",
"vue-loader": "^14.0.0", "vue-loader": "^16.0.0",
"vue-style-loader": "^4.0.0", "vue-style-loader": "4.1.2",
"webpack": "^4.44.0", "webpack": "4.46.0",
"webpack-dev-middleware": "^3.6.0", "webpack-dev-middleware": "3.7.3",
"webpack-hot-middleware": "^2.12.2", "webpack-hot-middleware": "2.24.3",
"webpack-merge": "^0.14.1" "webpack-merge": "0.14.1"
}, },
"engines": { "engines": {
"node": ">= 4.0.0", "node": ">= 4.0.0",

6
renovate.json Normal file
View file

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

View file

@ -46,7 +46,7 @@ export default {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val }) this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
window.addEventListener('resize', this.updateMobileState) window.addEventListener('resize', this.updateMobileState)
}, },
destroyed () { unmounted () {
window.removeEventListener('resize', this.updateMobileState) window.removeEventListener('resize', this.updateMobileState)
}, },
computed: { computed: {
@ -65,7 +65,7 @@ export default {
} }
} }
}, },
shout () { return this.$store.state.shout.channel.state === 'joined' }, shout () { return this.$store.state.shout.joined },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel && return this.$store.state.instance.showInstanceSpecificPanel &&

View file

@ -572,7 +572,7 @@ nav {
.fade-enter-active, .fade-leave-active { .fade-enter-active, .fade-leave-active {
transition: opacity .2s transition: opacity .2s
} }
.fade-enter, .fade-leave-active { .fade-enter-from, .fade-leave-active {
opacity: 0 opacity: 0
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
id="app" id="app-loaded"
:style="bgStyle" :style="bgStyle"
> >
<div <div
@ -59,7 +59,7 @@
<UserReportingModal /> <UserReportingModal />
<PostStatusModal /> <PostStatusModal />
<SettingsModal /> <SettingsModal />
<portal-target name="modal" /> <div id="modal" />
<GlobalNoticeList /> <GlobalNoticeList />
</div> </div>
</template> </template>

View file

@ -30,3 +30,5 @@ $fallback--attachmentRadius: 10px;
$fallback--chatMessageRadius: 10px; $fallback--chatMessageRadius: 10px;
$fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; $fallback--buttonShadow: 0px 0px 2px 0px rgba(0, 0, 0, 1), 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset;
$status-margin: 0.75em;

View file

@ -1,7 +1,13 @@
import Vue from 'vue' import { createApp } from 'vue'
import VueRouter from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import routes from './routes' import vClickOutside from 'click-outside-vue3'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import App from '../App.vue' import App from '../App.vue'
import routes from './routes'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth } from '../services/window_utils/window_utils' import { windowWidth } from '../services/window_utils/window_utils'
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
@ -115,6 +121,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('nsfwCensorImage') copyInstanceOption('nsfwCensorImage')
copyInstanceOption('background') copyInstanceOption('background')
copyInstanceOption('hidePostStats') copyInstanceOption('hidePostStats')
copyInstanceOption('hideBotIndication')
copyInstanceOption('hideUserStats') copyInstanceOption('hideUserStats')
copyInstanceOption('hideFilteredStatuses') copyInstanceOption('hideFilteredStatuses')
copyInstanceOption('logo') copyInstanceOption('logo')
@ -366,25 +373,32 @@ const afterStoreSetup = async ({ store, i18n }) => {
getTOS({ store }) getTOS({ store })
getStickers({ store }) getStickers({ store })
const router = new VueRouter({ const router = createRouter({
mode: 'history', history: createWebHistory(),
routes: routes(store), routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => { scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) { if (to.matched.some(m => m.meta.dontScroll)) {
return false return false
} }
return savedPosition || { x: 0, y: 0 } return savedPosition || { left: 0, top: 0 }
} }
}) })
/* eslint-disable no-new */ const app = createApp(App)
return new Vue({
router, app.use(router)
store, app.use(store)
i18n, app.use(i18n)
el: '#app',
render: h => h(App) app.use(vClickOutside)
}) app.use(VBodyScrollLock)
app.component('FAIcon', FontAwesomeIcon)
app.component('FALayers', FontAwesomeLayers)
app.mount('#app')
return app
} }
export default afterStoreSetup export default afterStoreSetup

View file

@ -39,12 +39,6 @@ export default (store) => {
: store.state.instance.redirectRootNoLogin) || '/main/all' : store.state.instance.redirectRootNoLogin) || '/main/all'
} }
}, },
// Redirects from Mastodon, Soapbox FE, etc. to fix old bookmarks
{ path: '/@:username/posts/:statusId', redirect: '/notice/:statusId' },
{ path: '/@:username/:statusId', redirect: '/notice/:statusId' },
{ path: '/:username/status/:statusId', redirect: '/notice/:statusId' },
{ name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline }, { name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline }, { name: 'public-timeline', path: '/main/public', component: PublicTimeline },
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
@ -52,7 +46,7 @@ export default (store) => {
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{ name: 'remote-user-profile-acct', { name: 'remote-user-profile-acct',
path: '/remote-users/(@?):username([^/@]+)@:hostname([^/@]+)', path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
component: RemoteUserResolver, component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute beforeEnter: validateAuthenticatedRoute
}, },
@ -75,7 +69,7 @@ export default (store) => {
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) }, { name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute }, { name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About }, { name: 'about', path: '/about', component: About },
{ name: 'user-profile', path: '/(users/)?:name', component: UserProfile } { name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile }
] ]
if (store.state.instance.pleromaChatMessagesAvailable) { if (store.state.instance.pleromaChatMessagesAvailable) {

View file

@ -19,6 +19,7 @@
<script> <script>
export default { export default {
emits: ['resetAsyncComponent'],
methods: { methods: {
retry () { retry () {
this.$emit('resetAsyncComponent') this.$emit('resetAsyncComponent')

View file

@ -11,7 +11,12 @@ import {
faImage, faImage,
faVideo, faVideo,
faPlayCircle, faPlayCircle,
faTimes faTimes,
faStop,
faSearchPlus,
faTrashAlt,
faPencilAlt,
faAlignRight
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add( library.add(
@ -20,27 +25,39 @@ library.add(
faImage, faImage,
faVideo, faVideo,
faPlayCircle, faPlayCircle,
faTimes faTimes,
faStop,
faSearchPlus,
faTrashAlt,
faPencilAlt,
faAlignRight
) )
const Attachment = { const Attachment = {
props: [ props: [
'attachment', 'attachment',
'description',
'hideDescription',
'nsfw', 'nsfw',
'size', 'size',
'allowPlay',
'setMedia', 'setMedia',
'naturalSizeLoad' 'remove',
'shiftUp',
'shiftDn',
'edit'
], ],
data () { data () {
return { return {
localDescription: this.description || this.attachment.description,
nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage, nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw, hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
preloadImage: this.$store.getters.mergedConfig.preloadImage, preloadImage: this.$store.getters.mergedConfig.preloadImage,
loading: false, loading: false,
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'), img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
modalOpen: false, modalOpen: false,
showHidden: false showHidden: false,
flashLoaded: false,
showDescription: false
} }
}, },
components: { components: {
@ -49,8 +66,23 @@ const Attachment = {
VideoAttachment VideoAttachment
}, },
computed: { computed: {
classNames () {
return [
{
'-loading': this.loading,
'-nsfw-placeholder': this.hidden,
'-editable': this.edit !== undefined
},
'-type-' + this.type,
this.size && '-size-' + this.size,
`-${this.useContainFit ? 'contain' : 'cover'}-fit`
]
},
usePlaceholder () { usePlaceholder () {
return this.size === 'hide' || this.type === 'unknown' return this.size === 'hide'
},
useContainFit () {
return this.$store.getters.mergedConfig.useContainFit
}, },
placeholderName () { placeholderName () {
if (this.attachment.description === '' || !this.attachment.description) { if (this.attachment.description === '' || !this.attachment.description) {
@ -74,24 +106,33 @@ const Attachment = {
return this.nsfw && this.hideNsfwLocal && !this.showHidden return this.nsfw && this.hideNsfwLocal && !this.showHidden
}, },
isEmpty () { isEmpty () {
return (this.type === 'html' && !this.attachment.oembed) || this.type === 'unknown' return (this.type === 'html' && !this.attachment.oembed)
},
isSmall () {
return this.size === 'small'
},
fullwidth () {
if (this.size === 'hide') return false
return this.type === 'html' || this.type === 'audio' || this.type === 'unknown'
}, },
useModal () { useModal () {
const modalTypes = this.size === 'hide' ? ['image', 'video', 'audio'] let modalTypes = []
: this.mergedConfig.playVideosInModal switch (this.size) {
? ['image', 'video'] case 'hide':
case 'small':
modalTypes = ['image', 'video', 'audio', 'flash']
break
default:
modalTypes = this.mergedConfig.playVideosInModal
? ['image', 'video', 'flash']
: ['image'] : ['image']
break
}
return modalTypes.includes(this.type) return modalTypes.includes(this.type)
}, },
videoTag () {
return this.useModal ? 'button' : 'span'
},
...mapGetters(['mergedConfig']) ...mapGetters(['mergedConfig'])
}, },
watch: {
localDescription (newVal) {
this.onEdit(newVal)
}
},
methods: { methods: {
linkClicked ({ target }) { linkClicked ({ target }) {
if (target.tagName === 'A') { if (target.tagName === 'A') {
@ -100,12 +141,37 @@ const Attachment = {
}, },
openModal (event) { openModal (event) {
if (this.useModal) { if (this.useModal) {
event.stopPropagation() this.$emit('setMedia')
event.preventDefault() this.$store.dispatch('setCurrentMedia', this.attachment)
this.setMedia() } else if (this.type === 'unknown') {
this.$store.dispatch('setCurrent', this.attachment) window.open(this.attachment.url)
} }
}, },
openModalForce (event) {
this.$emit('setMedia')
this.$store.dispatch('setCurrentMedia', this.attachment)
},
onEdit (event) {
this.edit && this.edit(this.attachment, event)
},
onRemove () {
this.remove && this.remove(this.attachment)
},
onShiftUp () {
this.shiftUp && this.shiftUp(this.attachment)
},
onShiftDn () {
this.shiftDn && this.shiftDn(this.attachment)
},
stopFlash () {
this.$refs.flash.closePlayer()
},
setFlashLoaded (event) {
this.flashLoaded = event
},
toggleDescription () {
this.showDescription = !this.showDescription
},
toggleHidden (event) { toggleHidden (event) {
if ( if (
(this.mergedConfig.useOneClickNsfw && !this.showHidden) && (this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
@ -132,7 +198,7 @@ const Attachment = {
onImageLoad (image) { onImageLoad (image) {
const width = image.naturalWidth const width = image.naturalWidth
const height = image.naturalHeight const height = image.naturalHeight
this.naturalSizeLoad && this.naturalSizeLoad({ width, height }) this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
} }
} }
} }

View file

@ -0,0 +1,268 @@
@import '../../_variables.scss';
.Attachment {
display: inline-flex;
flex-direction: column;
position: relative;
align-self: flex-start;
line-height: 0;
height: 100%;
border-style: solid;
border-width: 1px;
border-radius: $fallback--attachmentRadius;
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
.attachment-wrapper {
flex: 1 1 auto;
height: 100%;
position: relative;
overflow: hidden;
}
.description-container {
flex: 0 1 0;
display: flex;
padding-top: 0.5em;
z-index: 1;
p {
flex: 1;
text-align: center;
line-height: 1.5;
padding: 0.5em;
margin: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
&.-static {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding-top: 0;
background: var(--popover);
box-shadow: var(--popupShadow);
}
}
.description-field {
flex: 1;
min-width: 0;
}
& .placeholder-container,
& .image-container,
& .audio-container,
& .video-container,
& .flash-container,
& .oembed-container {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
}
.image-container {
.image {
width: 100%;
height: 100%;
}
}
& .flash-container,
& .video-container {
& .flash,
& video {
width: 100%;
height: 100%;
object-fit: contain;
align-self: center;
}
}
.audio-container {
display: flex;
align-items: flex-end;
audio {
width: 100%;
height: 100%;
}
}
.placeholder-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 0.5em;
}
.play-icon {
position: absolute;
font-size: 64px;
top: calc(50% - 32px);
left: calc(50% - 32px);
color: rgba(255, 255, 255, 0.75);
text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
&::before {
margin: 0;
}
}
.attachment-buttons {
display: flex;
position: absolute;
right: 0;
top: 0;
margin-top: 0.5em;
margin-right: 0.5em;
z-index: 1;
.attachment-button {
padding: 0;
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
text-align: center;
width: 2em;
height: 2em;
margin-left: 0.5em;
font-size: 1.25em;
// TODO: theming? hard to theme with unknown background image color
background: rgba(230, 230, 230, 0.7);
.svg-inline--fa {
color: rgba(0, 0, 0, 0.6);
}
&:hover .svg-inline--fa {
color: rgba(0, 0, 0, 0.9);
}
}
}
.oembed-container {
line-height: 1.2em;
flex: 1 0 100%;
width: 100%;
margin-right: 15px;
display: flex;
img {
width: 100%;
}
.image {
flex: 1;
img {
border: 0px;
border-radius: 5px;
height: 100%;
object-fit: cover;
}
}
.text {
flex: 2;
margin: 8px;
word-break: break-all;
h1 {
font-size: 14px;
margin: 0px;
}
}
}
&.-size-small {
.play-icon {
zoom: 0.5;
opacity: 0.7;
}
.attachment-buttons {
zoom: 0.7;
opacity: 0.5;
}
}
&.-editable {
padding: 0.5em;
& .description-container,
& .attachment-buttons {
margin: 0;
}
}
&.-placeholder {
display: inline-block;
color: $fallback--link;
color: var(--postLink, $fallback--link);
overflow: hidden;
white-space: nowrap;
height: auto;
line-height: 1.5;
&:not(.-editable) {
border: none;
}
&.-editable {
display: flex;
flex-direction: row;
align-items: baseline;
& .description-container,
& .attachment-buttons {
margin: 0;
padding: 0;
position: relative;
}
.description-container {
flex: 1;
padding-left: 0.5em;
}
.attachment-buttons {
order: 99;
align-self: center;
}
}
a {
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
svg {
color: inherit;
}
}
&.-loading {
cursor: progress;
}
&.-contain-fit {
img,
canvas {
object-fit: contain;
}
}
&.-cover-fit {
img,
canvas {
object-fit: cover;
}
}
}

View file

@ -1,7 +1,8 @@
<template> <template>
<div <button
v-if="usePlaceholder" v-if="usePlaceholder"
:class="{ 'fullwidth': fullwidth }" class="Attachment -placeholder button-unstyled"
:class="classNames"
@click="openModal" @click="openModal"
> >
<a <a
@ -11,20 +12,53 @@
:href="attachment.url" :href="attachment.url"
:alt="attachment.description" :alt="attachment.description"
:title="attachment.description" :title="attachment.description"
@click.prevent
> >
<FAIcon :icon="placeholderIconClass" /> <FAIcon :icon="placeholderIconClass" />
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ placeholderName }} <b>{{ nsfw ? "NSFW / " : "" }}</b>{{ edit ? '' : placeholderName }}
</a> </a>
<div
v-if="edit || remove"
class="attachment-buttons"
>
<button
v-if="remove"
class="button-unstyled attachment-button"
@click.prevent="onRemove"
>
<FAIcon icon="trash-alt" />
</button>
</div> </div>
<div
v-if="size !== 'hide' && !hideDescription && (edit || localDescription || showDescription)"
class="description-container"
:class="{ '-static': !edit }"
>
<input
v-if="edit"
v-model="localDescription"
type="text"
class="description-field"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
>
<p v-else>
{{ localDescription }}
</p>
</div>
</button>
<div <div
v-else v-else
class="Attachment"
:class="classNames"
>
<div
v-show="!isEmpty" v-show="!isEmpty"
class="attachment" class="attachment-wrapper"
:class="{[type]: true, loading, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}"
> >
<a <a
v-if="hidden" v-if="hidden"
class="image-attachment" class="image-container"
:href="attachment.url" :href="attachment.url"
:alt="attachment.description" :alt="attachment.description"
:title="attachment.description" :title="attachment.description"
@ -34,7 +68,6 @@
:key="nsfwImage" :key="nsfwImage"
class="nsfw" class="nsfw"
:src="nsfwImage" :src="nsfwImage"
:class="{'small': isSmall}"
> >
<FAIcon <FAIcon
v-if="type === 'video'" v-if="type === 'video'"
@ -42,21 +75,75 @@
icon="play-circle" icon="play-circle"
/> />
</a> </a>
<div
v-if="!hidden"
class="attachment-buttons"
>
<button <button
v-if="nsfw && hideNsfwLocal && !hidden" v-if="type === 'flash' && flashLoaded"
class="button-unstyled hider" class="button-unstyled attachment-button"
:title="$t('status.attachment_stop_flash')"
@click.prevent="stopFlash"
>
<FAIcon icon="stop" />
</button>
<button
v-if="attachment.description && size !== 'small' && !edit && type !== 'unknown'"
class="button-unstyled attachment-button"
:title="$t('status.show_attachment_description')"
@click.prevent="toggleDescription"
>
<FAIcon icon="align-right" />
</button>
<button
v-if="!useModal && type !== 'unknown'"
class="button-unstyled attachment-button"
:title="$t('status.show_attachment_in_modal')"
@click.prevent="openModalForce"
>
<FAIcon icon="search-plus" />
</button>
<button
v-if="nsfw && hideNsfwLocal"
class="button-unstyled attachment-button"
:title="$t('status.hide_attachment')"
@click.prevent="toggleHidden" @click.prevent="toggleHidden"
> >
<FAIcon icon="times" /> <FAIcon icon="times" />
</button> </button>
<button
v-if="shiftUp"
class="button-unstyled attachment-button"
:title="$t('status.move_up')"
@click.prevent="onShiftUp"
>
<FAIcon icon="chevron-left" />
</button>
<button
v-if="shiftDn"
class="button-unstyled attachment-button"
:title="$t('status.move_down')"
@click.prevent="onShiftDn"
>
<FAIcon icon="chevron-right" />
</button>
<button
v-if="remove"
class="button-unstyled attachment-button"
:title="$t('status.remove_attachment')"
@click.prevent="onRemove"
>
<FAIcon icon="trash-alt" />
</button>
</div>
<a <a
v-if="type === 'image' && (!hidden || preloadImage)" v-if="type === 'image' && (!hidden || preloadImage)"
class="image-attachment" class="image-container"
:class="{'hidden': hidden && preloadImage }" :class="{'-hidden': hidden && preloadImage }"
:href="attachment.url" :href="attachment.url"
target="_blank" target="_blank"
@click="openModal" @click.stop.prevent="openModal"
> >
<StillImage <StillImage
class="image" class="image"
@ -69,26 +156,48 @@
</a> </a>
<a <a
v-if="type === 'unknown' && !hidden"
class="placeholder-container"
:href="attachment.url"
target="_blank"
>
<FAIcon
size="5x"
:icon="placeholderIconClass"
/>
<p>
{{ localDescription }}
</p>
</a>
<component
:is="videoTag"
v-if="type === 'video' && !hidden" v-if="type === 'video' && !hidden"
class="video-container" class="video-container"
:class="{'small': isSmall}" :class="{ 'button-unstyled': 'isModal' }"
:href="allowPlay ? undefined : attachment.url" :href="attachment.url"
@click="openModal" @click.stop.prevent="openModal"
> >
<VideoAttachment <VideoAttachment
class="video" class="video"
:attachment="attachment" :attachment="attachment"
:controls="allowPlay" :controls="!useModal"
@play="$emit('play')" @play="$emit('play')"
@pause="$emit('pause')" @pause="$emit('pause')"
/> />
<FAIcon <FAIcon
v-if="!allowPlay" v-if="useModal"
class="play-icon" class="play-icon"
icon="play-circle" icon="play-circle"
/> />
</a> </component>
<span
v-if="type === 'audio' && !hidden"
class="audio-container"
:href="attachment.url"
@click.stop.prevent="openModal"
>
<audio <audio
v-if="type === 'audio'" v-if="type === 'audio'"
:src="attachment.url" :src="attachment.url"
@ -98,10 +207,11 @@
@play="$emit('play')" @play="$emit('play')"
@pause="$emit('pause')" @pause="$emit('pause')"
/> />
</span>
<div <div
v-if="type === 'html' && attachment.oembed" v-if="type === 'html' && attachment.oembed"
class="oembed" class="oembed-container"
@click.prevent="linkClicked" @click.prevent="linkClicked"
> >
<div <div
@ -118,211 +228,41 @@
</div> </div>
</div> </div>
<span
v-if="type === 'flash' && !hidden"
class="flash-container"
:href="attachment.url"
@click.stop.prevent="openModal"
>
<Flash <Flash
v-if="type === 'flash'" ref="flash"
class="flash"
:src="attachment.large_thumb_url || attachment.url" :src="attachment.large_thumb_url || attachment.url"
@playerOpened="setFlashLoaded(true)"
@playerClosed="setFlashLoaded(false)"
/> />
</span>
</div>
<div
v-if="size !== 'hide' && !hideDescription && (edit || (localDescription && showDescription))"
class="description-container"
:class="{ '-static': !edit }"
>
<input
v-if="edit"
v-model="localDescription"
type="text"
class="description-field"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
>
<p v-else>
{{ localDescription }}
</p>
</div>
</div> </div>
</template> </template>
<script src="./attachment.js"></script> <script src="./attachment.js"></script>
<style lang="scss"> <style src="./attachment.scss" lang="scss"></style>
@import '../../_variables.scss';
.attachments {
display: flex;
flex-wrap: wrap;
.non-gallery {
max-width: 100%;
}
.placeholder {
display: inline-block;
padding: 0.3em 1em 0.3em 0;
color: $fallback--link;
color: var(--postLink, $fallback--link);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 100%;
svg {
color: inherit;
}
}
.nsfw-placeholder {
cursor: pointer;
&.loading {
cursor: progress;
}
}
.attachment {
position: relative;
margin-top: 0.5em;
align-self: flex-start;
line-height: 0;
border-style: solid;
border-width: 1px;
border-radius: $fallback--attachmentRadius;
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
overflow: hidden;
}
.non-gallery.attachment {
&.flash,
&.video {
flex: 1 0 40%;
}
.nsfw {
height: 260px;
}
.small {
height: 120px;
flex-grow: 0;
}
.video {
height: 260px;
display: flex;
}
video {
max-height: 100%;
object-fit: contain;
}
}
.fullwidth {
flex-basis: 100%;
}
// fixes small gap below video
&.video {
line-height: 0;
}
.video-container {
display: flex;
max-height: 100%;
}
.video {
width: 100%;
height: 100%;
}
.play-icon {
position: absolute;
font-size: 64px;
top: calc(50% - 32px);
left: calc(50% - 32px);
color: rgba(255, 255, 255, 0.75);
text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
}
.play-icon::before {
margin: 0;
}
&.html {
flex-basis: 90%;
width: 100%;
display: flex;
}
.hider {
position: absolute;
right: 0;
margin: 10px;
padding: 0;
z-index: 4;
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
text-align: center;
width: 2em;
height: 2em;
font-size: 1.25em;
// TODO: theming? hard to theme with unknown background image color
background: rgba(230, 230, 230, 0.7);
.svg-inline--fa {
color: rgba(0, 0, 0, 0.6);
}
&:hover .svg-inline--fa {
color: rgba(0, 0, 0, 0.9);
}
}
video {
z-index: 0;
}
audio {
width: 100%;
}
img.media-upload {
line-height: 0;
max-height: 200px;
max-width: 100%;
}
.oembed {
line-height: 1.2em;
flex: 1 0 100%;
width: 100%;
margin-right: 15px;
display: flex;
img {
width: 100%;
}
.image {
flex: 1;
img {
border: 0px;
border-radius: 5px;
height: 100%;
object-fit: cover;
}
}
.text {
flex: 2;
margin: 8px;
word-break: break-all;
h1 {
font-size: 14px;
margin: 0px;
}
}
}
.image-attachment {
&,
& .image {
width: 100%;
height: 100%;
}
&.hidden {
display: none;
}
.nsfw {
object-fit: cover;
width: 100%;
height: 100%;
}
img {
image-orientation: from-image; // NOTE: only FF supports this
}
}
}
</style>

View file

@ -1,3 +1,4 @@
import { h, resolveComponent } from 'vue'
import LoginForm from '../login_form/login_form.vue' import LoginForm from '../login_form/login_form.vue'
import MFARecoveryForm from '../mfa_form/recovery_form.vue' import MFARecoveryForm from '../mfa_form/recovery_form.vue'
import MFATOTPForm from '../mfa_form/totp_form.vue' import MFATOTPForm from '../mfa_form/totp_form.vue'
@ -5,8 +6,8 @@ import { mapGetters } from 'vuex'
const AuthForm = { const AuthForm = {
name: 'AuthForm', name: 'AuthForm',
render (createElement) { render () {
return createElement('component', { is: this.authForm }) return h(resolveComponent(this.authForm))
}, },
computed: { computed: {
authForm () { authForm () {

View file

@ -4,7 +4,7 @@
<UserAvatar <UserAvatar
class="avatar" class="avatar"
:user="user" :user="user"
@click.prevent.native="toggleUserExpanded" @click.prevent="toggleUserExpanded"
/> />
</router-link> </router-link>
<div <div

View file

@ -9,7 +9,7 @@ const Bookmarks = {
components: { components: {
Timeline Timeline
}, },
destroyed () { unmounted () {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' }) this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
} }
} }

View file

@ -57,7 +57,7 @@ const Chat = {
}) })
this.setChatLayout() this.setChatLayout()
}, },
destroyed () { unmounted () {
window.removeEventListener('scroll', this.handleScroll) window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleLayoutChange) window.removeEventListener('resize', this.handleLayoutChange)
this.unsetChatLayout() this.unsetChatLayout()

View file

@ -26,7 +26,6 @@
/> />
</div> </div>
</div> </div>
<template>
<div <div
ref="scrollable" ref="scrollable"
class="scrollable-message-list" class="scrollable-message-list"
@ -92,7 +91,6 @@
@resize="handleResize" @resize="handleResize"
/> />
</div> </div>
</template>
</div> </div>
</div> </div>
</div> </div>

View file

@ -27,6 +27,7 @@ const ChatMessage = {
'chatViewItem', 'chatViewItem',
'hoveredMessageChain' 'hoveredMessageChain'
], ],
emits: ['hover'],
components: { components: {
Popover, Popover,
Attachment, Attachment,

View file

@ -1,6 +1,7 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.chat-message-wrapper { .chat-message-wrapper {
&.hovered-message-chain { &.hovered-message-chain {
.animated.Avatar { .animated.Avatar {
canvas { canvas {
@ -40,6 +41,12 @@
.chat-message { .chat-message {
display: flex; display: flex;
padding-bottom: 0.5em; padding-bottom: 0.5em;
.status-body:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
--_still-image-label-visibility: hidden;
}
} }
.avatar-wrapper { .avatar-wrapper {
@ -62,10 +69,6 @@
&.with-media { &.with-media {
width: 100%; width: 100%;
.gallery-row {
overflow: hidden;
}
.status { .status {
width: 100%; width: 100%;
} }

View file

@ -1,11 +1,12 @@
import Vue from 'vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import UserAvatar from '../user_avatar/user_avatar.vue' import UserAvatar from '../user_avatar/user_avatar.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
export default Vue.component('chat-title', { export default {
name: 'ChatTitle', name: 'ChatTitle',
components: { components: {
UserAvatar UserAvatar,
RichContent
}, },
props: [ props: [
'user', 'withAvatar' 'user', 'withAvatar'
@ -23,4 +24,4 @@ export default Vue.component('chat-title', {
return generateProfileLink(user.id, user.screen_name) return generateProfileLink(user.id, user.screen_name)
} }
} }
}) }

View file

@ -1,5 +1,4 @@
<template> <template>
<!-- eslint-disable vue/no-v-html -->
<div <div
class="chat-title" class="chat-title"
:title="title" :title="title"
@ -14,12 +13,14 @@
height="23px" height="23px"
/> />
</router-link> </router-link>
<span <RichContent
v-if="user"
class="username" class="username"
v-html="htmlTitle" :title="'@'+user.screen_name_ui"
:html="htmlTitle"
:emoji="user.emoji || []"
/> />
</div> </div>
<!-- eslint-enable vue/no-v-html -->
</template> </template>
<script src="./chat_title.js"></script> <script src="./chat_title.js"></script>
@ -34,6 +35,8 @@
white-space: nowrap; white-space: nowrap;
align-items: center; align-items: center;
--emoji-size: 14px;
.username { .username {
max-width: 100%; max-width: 100%;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -41,14 +44,6 @@
display: inline; display: inline;
word-wrap: break-word; word-wrap: break-word;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
.emoji {
width: 14px;
height: 14px;
vertical-align: middle;
object-fit: contain
}
} }
.Avatar { .Avatar {

View file

@ -6,9 +6,9 @@
<input <input
type="checkbox" type="checkbox"
:disabled="disabled" :disabled="disabled"
:checked="checked" :checked="modelValue"
:indeterminate.prop="indeterminate" :indeterminate="indeterminate"
@change="$emit('change', $event.target.checked)" @change="$emit('update:modelValue', $event.target.checked)"
> >
<i class="checkbox-indicator" /> <i class="checkbox-indicator" />
<span <span
@ -22,12 +22,9 @@
<script> <script>
export default { export default {
model: { emits: ['update:modelValue'],
prop: 'checked',
event: 'change'
},
props: [ props: [
'checked', 'modelValue',
'indeterminate', 'indeterminate',
'disabled' 'disabled'
] ]

View file

@ -11,28 +11,28 @@
</label> </label>
<Checkbox <Checkbox
v-if="typeof fallback !== 'undefined' && showOptionalTickbox" v-if="typeof fallback !== 'undefined' && showOptionalTickbox"
:checked="present" :model-value="present"
:disabled="disabled" :disabled="disabled"
class="opt" class="opt"
@change="$emit('input', typeof value === 'undefined' ? fallback : undefined)" @update:modelValue="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
/> />
<div class="input color-input-field"> <div class="input color-input-field">
<input <input
:id="name + '-t'" :id="name + '-t'"
class="textColor unstyled" class="textColor unstyled"
type="text" type="text"
:value="value || fallback" :value="modelValue || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
@input="$emit('input', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
> >
<input <input
v-if="validColor" v-if="validColor"
:id="name" :id="name"
class="nativeColor unstyled" class="nativeColor unstyled"
type="color" type="color"
:value="value || fallback" :value="modelValue || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
@input="$emit('input', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
> >
<div <div
v-if="transparentColor" v-if="transparentColor"
@ -67,7 +67,7 @@ export default {
}, },
// Color value, should be required but vue cannot tell the difference // Color value, should be required but vue cannot tell the difference
// between "property missing" and "property set to undefined" // between "property missing" and "property set to undefined"
value: { modelValue: {
required: false, required: false,
type: String, type: String,
default: undefined default: undefined
@ -91,18 +91,19 @@ export default {
default: true default: true
} }
}, },
emits: ['update:modelValue'],
computed: { computed: {
present () { present () {
return typeof this.value !== 'undefined' return typeof this.modelValue !== 'undefined'
}, },
validColor () { validColor () {
return hex2rgb(this.value || this.fallback) return hex2rgb(this.modelValue || this.fallback)
}, },
transparentColor () { transparentColor () {
return this.value === 'transparent' return this.modelValue === 'transparent'
}, },
computedColor () { computedColor () {
return this.value && this.value.startsWith('--') return this.modelValue && this.modelValue.startsWith('--')
} }
} }
} }

View file

@ -1,5 +1,19 @@
import { reduce, filter, findIndex, clone, get } from 'lodash' import { reduce, filter, findIndex, clone, get } from 'lodash'
import Status from '../status/status.vue' import Status from '../status/status.vue'
import ThreadTree from '../thread_tree/thread_tree.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faAngleDoubleDown,
faAngleDoubleLeft,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
library.add(
faAngleDoubleDown,
faAngleDoubleLeft,
faChevronLeft
)
const sortById = (a, b) => { const sortById = (a, b) => {
const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id const idA = a.type === 'retweet' ? a.retweeted_status.id : a.id
@ -35,7 +49,10 @@ const conversation = {
data () { data () {
return { return {
highlight: null, highlight: null,
expanded: false expanded: false,
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
statusContentPropertiesObject: {},
inlineDivePosition: null
} }
}, },
props: [ props: [
@ -53,12 +70,50 @@ const conversation = {
} }
}, },
computed: { computed: {
hideStatus () { maxDepthToShowByDefault () {
if (this.$refs.statusComponent && this.$refs.statusComponent[0]) { // maxDepthInThread = max number of depths that is *visible*
return this.virtualHidden && this.$refs.statusComponent[0].suspendable // since our depth starts with 0 and "showing" means "showing children"
} else { // there is a -2 here
return this.virtualHidden const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2
return maxDepth >= 1 ? maxDepth : 1
},
displayStyle () {
return this.$store.getters.mergedConfig.conversationDisplay
},
isTreeView () {
return !this.isLinearView
},
treeViewIsSimple () {
return !this.$store.getters.mergedConfig.conversationTreeAdvanced
},
isLinearView () {
return this.displayStyle === 'linear'
},
shouldFadeAncestors () {
return this.$store.getters.mergedConfig.conversationTreeFadeAncestors
},
otherRepliesButtonPosition () {
return this.$store.getters.mergedConfig.conversationOtherRepliesButton
},
showOtherRepliesButtonBelowStatus () {
return this.otherRepliesButtonPosition === 'below'
},
showOtherRepliesButtonInsideStatus () {
return this.otherRepliesButtonPosition === 'inside'
},
suspendable () {
if (this.isTreeView) {
return Object.entries(this.statusContentProperties)
.every(([k, prop]) => !prop.replying && prop.mediaPlaying.length === 0)
} }
if (this.$refs.statusComponent && this.$refs.statusComponent[0]) {
return this.$refs.statusComponent.every(s => s.suspendable)
} else {
return true
}
},
hideStatus () {
return this.virtualHidden && this.suspendable
}, },
status () { status () {
return this.$store.state.statuses.allStatusesObject[this.statusId] return this.$store.state.statuses.allStatusesObject[this.statusId]
@ -90,6 +145,121 @@ const conversation = {
return sortAndFilterConversation(conversation, this.status) return sortAndFilterConversation(conversation, this.status)
}, },
statusMap () {
return this.conversation.reduce((res, s) => {
res[s.id] = s
return res
}, {})
},
threadTree () {
const reverseLookupTable = this.conversation.reduce((table, status, index) => {
table[status.id] = index
return table
}, {})
const threads = this.conversation.reduce((a, cur) => {
const id = cur.id
a.forest[id] = this.getReplies(id)
.map(s => s.id)
return a
}, {
forest: {}
})
const walk = (forest, topLevel, depth = 0, processed = {}) => topLevel.map(id => {
if (processed[id]) {
return []
}
processed[id] = true
return [{
status: this.conversation[reverseLookupTable[id]],
id,
depth
}, walk(forest, forest[id], depth + 1, processed)].reduce((a, b) => a.concat(b), [])
}).reduce((a, b) => a.concat(b), [])
const linearized = walk(threads.forest, this.topLevel.map(k => k.id))
return linearized
},
replyIds () {
return this.conversation.map(k => k.id)
.reduce((res, id) => {
res[id] = (this.replies[id] || []).map(k => k.id)
return res
}, {})
},
totalReplyCount () {
const sizes = {}
const subTreeSizeFor = (id) => {
if (sizes[id]) {
return sizes[id]
}
sizes[id] = 1 + this.replyIds[id].map(cid => subTreeSizeFor(cid)).reduce((a, b) => a + b, 0)
return sizes[id]
}
this.conversation.map(k => k.id).map(subTreeSizeFor)
return Object.keys(sizes).reduce((res, id) => {
res[id] = sizes[id] - 1 // exclude itself
return res
}, {})
},
totalReplyDepth () {
const depths = {}
const subTreeDepthFor = (id) => {
if (depths[id]) {
return depths[id]
}
depths[id] = 1 + this.replyIds[id].map(cid => subTreeDepthFor(cid)).reduce((a, b) => a > b ? a : b, 0)
return depths[id]
}
this.conversation.map(k => k.id).map(subTreeDepthFor)
return Object.keys(depths).reduce((res, id) => {
res[id] = depths[id] - 1 // exclude itself
return res
}, {})
},
depths () {
return this.threadTree.reduce((a, k) => {
a[k.id] = k.depth
return a
}, {})
},
topLevel () {
const topLevel = this.conversation.reduce((tl, cur) =>
tl.filter(k => this.getReplies(cur.id).map(v => v.id).indexOf(k.id) === -1), this.conversation)
return topLevel
},
otherTopLevelCount () {
return this.topLevel.length - 1
},
showingTopLevel () {
if (this.canDive && this.diveRoot) {
return [this.statusMap[this.diveRoot]]
}
return this.topLevel
},
diveRoot () {
const statusId = this.inlineDivePosition || this.statusId
const isTopLevel = !this.parentOf(statusId)
return isTopLevel ? null : statusId
},
diveDepth () {
return this.canDive && this.diveRoot ? this.depths[this.diveRoot] : 0
},
diveMode () {
return this.canDive && !!this.diveRoot
},
shouldShowAllConversationButton () {
// The "show all conversation" button tells the user that there exist
// other toplevel statuses, so do not show it if there is only a single root
return this.isTreeView && this.isExpanded && this.diveMode && this.topLevel.length > 1
},
shouldShowAncestors () {
return this.isTreeView && this.isExpanded && this.ancestorsOf(this.diveRoot).length
},
replies () { replies () {
let i = 1 let i = 1
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
@ -109,15 +279,71 @@ const conversation = {
}, {}) }, {})
}, },
isExpanded () { isExpanded () {
return this.expanded || this.isPage return !!(this.expanded || this.isPage)
}, },
hiddenStyle () { hiddenStyle () {
const height = (this.status && this.status.virtualHeight) || '120px' const height = (this.status && this.status.virtualHeight) || '120px'
return this.virtualHidden ? { height } : {} return this.virtualHidden ? { height } : {}
},
threadDisplayStatus () {
return this.conversation.reduce((a, k) => {
const id = k.id
const depth = this.depths[id]
const status = (() => {
if (this.threadDisplayStatusObject[id]) {
return this.threadDisplayStatusObject[id]
}
if ((depth - this.diveDepth) <= this.maxDepthToShowByDefault) {
return 'showing'
} else {
return 'hidden'
}
})()
a[id] = status
return a
}, {})
},
statusContentProperties () {
return this.conversation.reduce((a, k) => {
const id = k.id
const props = (() => {
const def = {
showingTall: false,
expandingSubject: false,
showingLongSubject: false,
isReplying: false,
mediaPlaying: []
}
if (this.statusContentPropertiesObject[id]) {
return {
...def,
...this.statusContentPropertiesObject[id]
}
}
return def
})()
a[id] = props
return a
}, {})
},
canDive () {
return this.isTreeView && this.isExpanded
},
focused () {
return (id) => {
return (this.isExpanded) && id === this.highlight
}
},
maybeHighlight () {
return this.isExpanded ? this.highlight : null
} }
}, },
components: { components: {
Status Status,
ThreadTree
}, },
watch: { watch: {
statusId (newVal, oldVal) { statusId (newVal, oldVal) {
@ -132,6 +358,8 @@ const conversation = {
expanded (value) { expanded (value) {
if (value) { if (value) {
this.fetchConversation() this.fetchConversation()
} else {
this.resetDisplayState()
} }
}, },
virtualHidden (value) { virtualHidden (value) {
@ -161,8 +389,8 @@ const conversation = {
getReplies (id) { getReplies (id) {
return this.replies[id] || [] return this.replies[id] || []
}, },
focused (id) { getHighlight () {
return (this.isExpanded) && id === this.statusId return this.isExpanded ? this.highlight : null
}, },
setHighlight (id) { setHighlight (id) {
if (!id) return if (!id) return
@ -170,15 +398,139 @@ const conversation = {
this.$store.dispatch('fetchFavsAndRepeats', id) this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id) this.$store.dispatch('fetchEmojiReactionsBy', id)
}, },
getHighlight () {
return this.isExpanded ? this.highlight : null
},
toggleExpanded () { toggleExpanded () {
this.expanded = !this.expanded this.expanded = !this.expanded
}, },
getConversationId (statusId) { getConversationId (statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId] const status = this.$store.state.statuses.allStatusesObject[statusId]
return get(status, 'retweeted_status.statusnet_conversation_id', get(status, 'statusnet_conversation_id')) return get(status, 'retweeted_status.statusnet_conversation_id', get(status, 'statusnet_conversation_id'))
},
setThreadDisplay (id, nextStatus) {
this.threadDisplayStatusObject = {
...this.threadDisplayStatusObject,
[id]: nextStatus
}
},
toggleThreadDisplay (id) {
const curStatus = this.threadDisplayStatus[id]
const nextStatus = curStatus === 'showing' ? 'hidden' : 'showing'
this.setThreadDisplay(id, nextStatus)
},
setThreadDisplayRecursively (id, nextStatus) {
this.setThreadDisplay(id, nextStatus)
this.getReplies(id).map(k => k.id).map(id => this.setThreadDisplayRecursively(id, nextStatus))
},
showThreadRecursively (id) {
this.setThreadDisplayRecursively(id, 'showing')
},
setStatusContentProperty (id, name, value) {
this.statusContentPropertiesObject = {
...this.statusContentPropertiesObject,
[id]: {
...this.statusContentPropertiesObject[id],
[name]: value
}
}
},
toggleStatusContentProperty (id, name) {
this.setStatusContentProperty(id, name, !this.statusContentProperties[id][name])
},
leastVisibleAncestor (id) {
let cur = id
let parent = this.parentOf(cur)
while (cur) {
// if the parent is showing it means cur is visible
if (this.threadDisplayStatus[parent] === 'showing') {
return cur
}
parent = this.parentOf(parent)
cur = this.parentOf(cur)
}
// nothing found, fall back to toplevel
return this.topLevel[0] ? this.topLevel[0].id : undefined
},
diveIntoStatus (id, preventScroll) {
this.tryScrollTo(id)
},
diveToTopLevel () {
this.tryScrollTo(this.topLevelAncestorOrSelfId(this.diveRoot) || this.topLevel[0].id)
},
// only used when we are not on a page
undive () {
this.inlineDivePosition = null
this.setHighlight(this.statusId)
},
tryScrollTo (id) {
if (!id) {
return
}
if (this.isPage) {
// set statusId
this.$router.push({ name: 'conversation', params: { id } })
} else {
this.inlineDivePosition = id
}
// Because the conversation can be unmounted when out of sight
// and mounted again when it comes into sight,
// the `mounted` or `created` function in `status` should not
// contain scrolling calls, as we do not want the page to jump
// when we scroll with an expanded conversation.
//
// Now the method is to rely solely on the `highlight` watcher
// in `status` components.
// In linear views, all statuses are rendered at all times, but
// in tree views, it is possible that a change in active status
// removes and adds status components (e.g. an originally child
// status becomes an ancestor status, and thus they will be
// different).
// Here, let the components be rendered first, in order to trigger
// the `highlight` watcher.
this.$nextTick(() => {
this.setHighlight(id)
})
},
goToCurrent () {
this.tryScrollTo(this.diveRoot || this.topLevel[0].id)
},
statusById (id) {
return this.statusMap[id]
},
parentOf (id) {
const status = this.statusById(id)
if (!status) {
return undefined
}
const { in_reply_to_status_id: parentId } = status
if (!this.statusMap[parentId]) {
return undefined
}
return parentId
},
parentOrSelf (id) {
return this.parentOf(id) || id
},
// Ancestors of some status, from top to bottom
ancestorsOf (id) {
const ancestors = []
let cur = this.parentOf(id)
while (cur) {
ancestors.unshift(this.statusMap[cur])
cur = this.parentOf(cur)
}
return ancestors
},
topLevelAncestorOrSelfId (id) {
let cur = id
let parent = this.parentOf(id)
while (parent) {
cur = this.parentOf(cur)
parent = this.parentOf(parent)
}
return cur
},
resetDisplayState () {
this.undive()
this.threadDisplayStatusObject = {}
} }
} }
} }

View file

@ -18,6 +18,146 @@
{{ $t('timeline.collapse') }} {{ $t('timeline.collapse') }}
</button> </button>
</div> </div>
<div class="conversation-body panel-body">
<div
v-if="isTreeView"
class="thread-body"
>
<div
v-if="shouldShowAllConversationButton"
class="conversation-dive-to-top-level-box"
>
<i18n-t
keypath="status.show_all_conversation_with_icon"
tag="button"
class="button-unstyled -link"
@click.prevent="diveToTopLevel"
scope="global"
>
<template #icon>
<FAIcon
icon="angle-double-left"
/>
</template>
<template #text>
<span>
{{ $tc('status.show_all_conversation', otherTopLevelCount, { numStatus: otherTopLevelCount }) }}
</span>
</template>
</i18n-t>
</div>
<div
v-if="shouldShowAncestors"
class="thread-ancestors"
>
<div
v-for="status in ancestorsOf(diveRoot)"
:key="status.id"
class="thread-ancestor"
:class="{'thread-ancestor-has-other-replies': getReplies(status.id).length > 1, '-faded': shouldFadeAncestors}"
>
<status
ref="statusComponent"
:inline-expanded="collapsable && isExpanded"
:statusoid="status"
:expandable="!isExpanded"
:show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]"
:focused="focused(status.id)"
:in-conversation="isExpanded"
:highlight="getHighlight()"
:replies="getReplies(status.id)"
:in-profile="inProfile"
:profile-user-id="profileUserId"
class="conversation-status status-fadein panel-body"
:simple-tree="treeViewIsSimple"
:toggle-thread-display="toggleThreadDisplay"
:thread-display-status="threadDisplayStatus"
:show-thread-recursively="showThreadRecursively"
:total-reply-count="totalReplyCount"
:total-reply-depth="totalReplyDepth"
:show-other-replies-as-button="showOtherRepliesButtonInsideStatus"
:dive="() => diveIntoStatus(status.id)"
:controlled-showing-tall="statusContentProperties[status.id].showingTall"
:controlled-expanding-subject="statusContentProperties[status.id].expandingSubject"
:controlled-showing-long-subject="statusContentProperties[status.id].showingLongSubject"
:controlled-replying="statusContentProperties[status.id].replying"
:controlled-media-playing="statusContentProperties[status.id].mediaPlaying"
:controlled-toggle-showing-tall="() => toggleStatusContentProperty(status.id, 'showingTall')"
:controlled-toggle-expanding-subject="() => toggleStatusContentProperty(status.id, 'expandingSubject')"
:controlled-toggle-showing-long-subject="() => toggleStatusContentProperty(status.id, 'showingLongSubject')"
:controlled-toggle-replying="() => toggleStatusContentProperty(status.id, 'replying')"
:controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
/>
<div
v-if="showOtherRepliesButtonBelowStatus && getReplies(status.id).length > 1"
class="thread-ancestor-dive-box"
>
<div
class="thread-ancestor-dive-box-inner"
>
<i18n-t
tag="button"
scope="global"
keypath="status.ancestor_follow_with_icon"
class="button-unstyled -link thread-tree-show-replies-button"
@click.prevent="diveIntoStatus(status.id)"
>
<template #icon>
<FAIcon
icon="angle-double-right"
/>
</template>
<template #text>
<span>
{{ $tc('status.ancestor_follow', getReplies(status.id).length - 1, { numReplies: getReplies(status.id).length - 1 }) }}
</span>
</template>
</i18n-t>
</div>
</div>
</div>
</div>
<thread-tree
v-for="status in showingTopLevel"
:key="status.id"
ref="statusComponent"
:depth="0"
:status="status"
:in-profile="inProfile"
:conversation="conversation"
:collapsable="collapsable"
:is-expanded="isExpanded"
:pinned-status-ids-object="pinnedStatusIdsObject"
:profile-user-id="profileUserId"
:focused="focused"
:get-replies="getReplies"
:highlight="maybeHighlight"
:set-highlight="setHighlight"
:toggle-expanded="toggleExpanded"
:simple="treeViewIsSimple"
:toggle-thread-display="toggleThreadDisplay"
:thread-display-status="threadDisplayStatus"
:show-thread-recursively="showThreadRecursively"
:total-reply-count="totalReplyCount"
:total-reply-depth="totalReplyDepth"
:status-content-properties="statusContentProperties"
:set-status-content-property="setStatusContentProperty"
:toggle-status-content-property="toggleStatusContentProperty"
:dive="canDive ? diveIntoStatus : undefined"
/>
</div>
<div
v-if="isLinearView"
class="thread-body"
>
<status <status
v-for="status in conversation" v-for="status in conversation"
:key="status.id" :key="status.id"
@ -33,10 +173,22 @@
:in-profile="inProfile" :in-profile="inProfile"
:profile-user-id="profileUserId" :profile-user-id="profileUserId"
class="conversation-status status-fadein panel-body" class="conversation-status status-fadein panel-body"
:toggle-thread-display="toggleThreadDisplay"
:thread-display-status="threadDisplayStatus"
:show-thread-recursively="showThreadRecursively"
:total-reply-count="totalReplyCount"
:total-reply-depth="totalReplyDepth"
:status-content-properties="statusContentProperties"
:set-status-content-property="setStatusContentProperty"
:toggle-status-content-property="toggleStatusContentProperty"
@goto="setHighlight" @goto="setHighlight"
@toggleExpanded="toggleExpanded" @toggleExpanded="toggleExpanded"
/> />
</div> </div>
</div>
</div>
<div <div
v-else v-else
:style="hiddenStyle" :style="hiddenStyle"
@ -49,6 +201,45 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.Conversation { .Conversation {
.conversation-dive-to-top-level-box {
padding: var(--status-margin, $status-margin);
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
border-radius: 0;
/* Make the button stretch along the whole row */
display: flex;
align-items: stretch;
flex-direction: column;
}
.thread-ancestors {
margin-left: var(--status-margin, $status-margin);
border-left: 2px solid var(--border, $fallback--border);
}
.thread-ancestor.-faded .StatusContent {
--link: var(--faintLink);
--text: var(--faint);
color: var(--text);
}
.thread-ancestor-dive-box {
padding-left: var(--status-margin, $status-margin);
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: var(--border, $fallback--border);
border-radius: 0;
/* Make the button stretch along the whole row */
&, &-inner {
display: flex;
align-items: stretch;
flex-direction: column;
}
}
.thread-ancestor-dive-box-inner {
padding: var(--status-margin, $status-margin);
}
.conversation-status { .conversation-status {
border-bottom-width: 1px; border-bottom-width: 1px;
border-bottom-style: solid; border-bottom-style: solid;
@ -56,12 +247,28 @@
border-radius: 0; border-radius: 0;
} }
&.-expanded { .thread-ancestor-has-other-replies .conversation-status,
.conversation-status:last-child { .thread-ancestor:last-child .conversation-status,
.thread-ancestor:last-child .thread-ancestor-dive-box,
&.-expanded .thread-tree .conversation-status {
border-bottom: none; border-bottom: none;
}
.thread-ancestors + .thread-tree > .conversation-status {
border-top-width: 1px;
border-top-style: solid;
border-top-color: var(--border, $fallback--border);
}
/* expanded conversation in timeline */
&.status-fadein.-expanded .thread-body {
border-left-width: 4px;
border-left-style: solid;
border-left-color: $fallback--cRed;
border-left-color: var(--cRed, $fallback--cRed);
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
} border-bottom: 1px solid var(--border, $fallback--border);
} }
} }
</style> </style>

View file

@ -34,7 +34,7 @@
<search-bar <search-bar
v-if="currentUser || !privateMode" v-if="currentUser || !privateMode"
@toggled="onSearchBarToggled" @toggled="onSearchBarToggled"
@click.stop.native @click.stop
/> />
<button <button
class="button-unstyled nav-icon" class="button-unstyled nav-icon"
@ -52,6 +52,7 @@
href="/pleroma/admin/#/login-pleroma" href="/pleroma/admin/#/login-pleroma"
class="nav-icon" class="nav-icon"
target="_blank" target="_blank"
@click.stop
> >
<FAIcon <FAIcon
fixed-width fixed-width

View file

@ -31,6 +31,7 @@ library.add(
*/ */
const EmojiInput = { const EmojiInput = {
emits: ['update:modelValue', 'shown'],
props: { props: {
suggest: { suggest: {
/** /**
@ -57,8 +58,7 @@ const EmojiInput = {
required: true, required: true,
type: Function type: Function
}, },
// TODO VUE3: change to modelValue, change 'input' event to 'input' modelValue: {
value: {
/** /**
* Used for v-model * Used for v-model
*/ */
@ -137,8 +137,8 @@ const EmojiInput = {
return (this.wordAtCaret || {}).word || '' return (this.wordAtCaret || {}).word || ''
}, },
wordAtCaret () { wordAtCaret () {
if (this.value && this.caret) { if (this.modelValue && this.caret) {
const word = Completion.wordAtPosition(this.value, this.caret - 1) || {} const word = Completion.wordAtPosition(this.modelValue, this.caret - 1) || {}
return word return word
} }
} }
@ -189,8 +189,11 @@ const EmojiInput = {
img: imageUrl || '' img: imageUrl || ''
})) }))
}, },
suggestions (newValue) { suggestions: {
handler (newValue) {
this.$nextTick(this.resize) this.$nextTick(this.resize)
},
deep: true
} }
}, },
methods: { methods: {
@ -225,13 +228,13 @@ const EmojiInput = {
} }
}, },
replace (replacement) { replace (replacement) {
const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement)
this.$emit('input', newValue) this.$emit('update:modelValue', newValue)
this.caret = 0 this.caret = 0
}, },
insert ({ insertion, keepOpen, surroundingSpace = true }) { insert ({ insertion, keepOpen, surroundingSpace = true }) {
const before = this.value.substring(0, this.caret) || '' const before = this.modelValue.substring(0, this.caret) || ''
const after = this.value.substring(this.caret) || '' const after = this.modelValue.substring(this.caret) || ''
/* Using a bit more smart approach to padding emojis with spaces: /* Using a bit more smart approach to padding emojis with spaces:
* - put a space before cursor if there isn't one already, unless we * - put a space before cursor if there isn't one already, unless we
@ -259,7 +262,7 @@ const EmojiInput = {
after after
].join('') ].join('')
this.keepOpen = keepOpen this.keepOpen = keepOpen
this.$emit('input', newValue) this.$emit('update:modelValue', newValue)
const position = this.caret + (insertion + spaceAfter + spaceBefore).length const position = this.caret + (insertion + spaceAfter + spaceBefore).length
if (!keepOpen) { if (!keepOpen) {
this.input.focus() this.input.focus()
@ -278,8 +281,8 @@ const EmojiInput = {
if (len > 0 || suggestion) { if (len > 0 || suggestion) {
const chosenSuggestion = suggestion || this.suggestions[this.highlighted] const chosenSuggestion = suggestion || this.suggestions[this.highlighted]
const replacement = chosenSuggestion.replacement const replacement = chosenSuggestion.replacement
const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement) const newValue = Completion.replaceWord(this.modelValue, this.wordAtCaret, replacement)
this.$emit('input', newValue) this.$emit('update:modelValue', newValue)
this.highlighted = 0 this.highlighted = 0
const position = this.wordAtCaret.start + replacement.length const position = this.wordAtCaret.start + replacement.length
@ -455,7 +458,7 @@ const EmojiInput = {
this.showPicker = false this.showPicker = false
this.setCaret(e) this.setCaret(e)
this.resize() this.resize()
this.$emit('input', e.target.value) this.$emit('update:modelValue', e.target.value)
}, },
onClickInput (e) { onClickInput (e) {
this.showPicker = false this.showPicker = false

View file

@ -1,3 +1,4 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -57,7 +58,7 @@ const EmojiPicker = {
} }
}, },
components: { components: {
StickerPicker: () => import('../sticker_picker/sticker_picker.vue'), StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
Checkbox Checkbox
}, },
methods: { methods: {
@ -79,7 +80,7 @@ const EmojiPicker = {
}, },
highlight (key) { highlight (key) {
const ref = this.$refs['group-' + key] const ref = this.$refs['group-' + key]
const top = ref[0].offsetTop const top = ref.offsetTop
this.setShowStickers(false) this.setShowStickers(false)
this.activeGroup = key this.activeGroup = key
this.$nextTick(() => { this.$nextTick(() => {
@ -96,7 +97,7 @@ const EmojiPicker = {
} }
}, },
triggerLoadMore (target) { triggerLoadMore (target) {
const ref = this.$refs['group-end-custom'][0] const ref = this.$refs['group-end-custom']
if (!ref) return if (!ref) return
const bottom = ref.offsetTop + ref.offsetHeight const bottom = ref.offsetTop + ref.offsetHeight
@ -119,7 +120,7 @@ const EmojiPicker = {
this.$nextTick(() => { this.$nextTick(() => {
this.emojisView.forEach(group => { this.emojisView.forEach(group => {
const ref = this.$refs['group-' + group.id] const ref = this.$refs['group-' + group.id]
if (ref[0].offsetTop <= top) { if (ref.offsetTop <= top) {
this.activeGroup = group.id this.activeGroup = group.id
} }
}) })

View file

@ -15,18 +15,8 @@ const Exporter = {
type: String, type: String,
default: 'export.csv' default: 'export.csv'
}, },
exportButtonLabel: { exportButtonLabel: { type: String },
type: String, processingMessage: { type: String }
default () {
return this.$t('exporter.export')
}
},
processingMessage: {
type: String,
default () {
return this.$t('exporter.processing')
}
}
}, },
data () { data () {
return { return {

View file

@ -7,14 +7,14 @@
spin spin
/> />
<span>{{ processingMessage }}</span> <span>{{ processingMessage || $t('exporter.processing') }}</span>
</div> </div>
<button <button
v-else v-else
class="btn button-default" class="btn button-default"
@click="process" @click="process"
> >
{{ exportButtonLabel }} {{ exportButtonLabel || $t('exporter.export') }}
</button> </button>
</div> </div>
</template> </template>

View file

@ -39,12 +39,13 @@ const Flash = {
this.player = 'error' this.player = 'error'
}) })
this.ruffleInstance = player this.ruffleInstance = player
this.$emit('playerOpened')
}) })
}, },
closePlayer () { closePlayer () {
console.log(this.ruffleInstance) this.ruffleInstance && this.ruffleInstance.remove()
this.ruffleInstance.remove()
this.player = false this.player = false
this.$emit('playerClosed')
} }
} }
} }

View file

@ -36,13 +36,6 @@
</p> </p>
</span> </span>
</button> </button>
<button
v-if="player"
class="button-unstyled hider"
@click="closePlayer"
>
<FAIcon icon="stop" />
</button>
</div> </div>
</template> </template>
@ -51,8 +44,9 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.Flash { .Flash {
display: inline-block;
width: 100%; width: 100%;
height: 260px; height: 100%;
position: relative; position: relative;
.player { .player {
@ -60,6 +54,16 @@
width: 100%; width: 100%;
} }
.placeholder {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg);
color: var(--link);
}
.hider { .hider {
top: 0; top: 0;
} }
@ -76,13 +80,5 @@
display: none; display: none;
visibility: 'hidden'; visibility: 'hidden';
} }
.placeholder {
height: 100%;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
} }
</style> </style>

View file

@ -1,6 +1,6 @@
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
export default { export default {
props: ['relationship', 'labelFollowing', 'buttonClass'], props: ['relationship', 'user', 'labelFollowing', 'buttonClass'],
data () { data () {
return { return {
inProgress: false inProgress: false
@ -14,7 +14,7 @@ export default {
if (this.inProgress || this.relationship.following) { if (this.inProgress || this.relationship.following) {
return this.$t('user_card.follow_unfollow') return this.$t('user_card.follow_unfollow')
} else if (this.relationship.requested) { } else if (this.relationship.requested) {
return this.$t('user_card.follow_again') return this.$t('user_card.follow_cancel')
} else { } else {
return this.$t('user_card.follow') return this.$t('user_card.follow')
} }
@ -29,11 +29,14 @@ export default {
} else { } else {
return this.$t('user_card.follow') return this.$t('user_card.follow')
} }
},
disabled () {
return this.inProgress || this.user.deactivated
} }
}, },
methods: { methods: {
onClick () { onClick () {
this.relationship.following ? this.unfollow() : this.follow() this.relationship.following || this.relationship.requested ? this.unfollow() : this.follow()
}, },
follow () { follow () {
this.inProgress = true this.inProgress = true

View file

@ -2,7 +2,7 @@
<button <button
class="btn button-default follow-button" class="btn button-default follow-button"
:class="{ toggled: isPressed }" :class="{ toggled: isPressed }"
:disabled="inProgress" :disabled="disabled"
:title="title" :title="title"
@click="onClick" @click="onClick"
> >

View file

@ -20,6 +20,7 @@
:relationship="relationship" :relationship="relationship"
:label-following="$t('user_card.follow_unfollow')" :label-following="$t('user_card.follow_unfollow')"
class="follow-card-follow-button" class="follow-card-follow-button"
:user="user"
/> />
</template> </template>
</div> </div>

View file

@ -1,4 +1,4 @@
import { set } from 'vue' import { set } from 'lodash'
import Select from '../select/select.vue' import Select from '../select/select.vue'
export default { export default {
@ -6,11 +6,12 @@ export default {
Select Select
}, },
props: [ props: [
'name', 'label', 'value', 'fallback', 'options', 'no-inherit' 'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
], ],
emits: ['update:modelValue'],
data () { data () {
return { return {
lValue: this.value, lValue: this.modelValue,
availableOptions: [ availableOptions: [
this.noInherit ? '' : 'inherit', this.noInherit ? '' : 'inherit',
'custom', 'custom',
@ -22,7 +23,7 @@ export default {
} }
}, },
beforeUpdate () { beforeUpdate () {
this.lValue = this.value this.lValue = this.modelValue
}, },
computed: { computed: {
present () { present () {
@ -37,7 +38,7 @@ export default {
}, },
set (v) { set (v) {
set(this.lValue, 'family', v) set(this.lValue, 'family', v)
this.$emit('input', this.lValue) this.$emit('update:modelValue', this.lValue)
} }
}, },
isCustom () { isCustom () {

View file

@ -15,13 +15,14 @@
class="opt exlcude-disabled" class="opt exlcude-disabled"
type="checkbox" type="checkbox"
:checked="present" :checked="present"
@input="$emit('input', typeof value === 'undefined' ? fallback : undefined)" @change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
> >
<label <label
v-if="typeof fallback !== 'undefined'" v-if="typeof fallback !== 'undefined'"
class="opt-l" class="opt-l"
:for="name + '-o'" :for="name + '-o'"
/> />
{{ ' ' }}
<Select <Select
:id="name + '-font-switcher'" :id="name + '-font-switcher'"
v-model="preset" v-model="preset"

View file

@ -1,15 +1,26 @@
import Attachment from '../attachment/attachment.vue' import Attachment from '../attachment/attachment.vue'
import { chunk, last, dropRight, sumBy } from 'lodash' import { sumBy, set } from 'lodash'
const Gallery = { const Gallery = {
props: [ props: [
'attachments', 'attachments',
'limitRows',
'descriptions',
'limit',
'nsfw', 'nsfw',
'setMedia' 'setMedia',
'size',
'editable',
'removeAttachment',
'shiftUpAttachment',
'shiftDnAttachment',
'editAttachment',
'grid'
], ],
data () { data () {
return { return {
sizes: {} sizes: {},
hidingLong: true
} }
}, },
components: { Attachment }, components: { Attachment },
@ -18,26 +29,70 @@ const Gallery = {
if (!this.attachments) { if (!this.attachments) {
return [] return []
} }
const rows = chunk(this.attachments, 3) const attachments = this.limit > 0
if (last(rows).length === 1 && rows.length > 1) { ? this.attachments.slice(0, this.limit)
// if 1 attachment on last row -> add it to the previous row instead : this.attachments
const lastAttachment = last(rows)[0] if (this.size === 'hide') {
const allButLastRow = dropRight(rows) return attachments.map(item => ({ minimal: true, items: [item] }))
last(allButLastRow).push(lastAttachment)
return allButLastRow
} }
const rows = this.grid
? [{ grid: true, items: attachments }]
: attachments.reduce((acc, attachment, i) => {
if (attachment.mimetype.includes('audio')) {
return [...acc, { audio: true, items: [attachment] }, { items: [] }]
}
if (!(
attachment.mimetype.includes('image') ||
attachment.mimetype.includes('video') ||
attachment.mimetype.includes('flash')
)) {
return [...acc, { minimal: true, items: [attachment] }, { items: [] }]
}
const maxPerRow = 3
const attachmentsRemaining = this.attachments.length - i + 1
const currentRow = acc[acc.length - 1].items
currentRow.push(attachment)
if (currentRow.length >= maxPerRow && attachmentsRemaining > maxPerRow) {
return [...acc, { items: [] }]
} else {
return acc
}
}, [{ items: [] }]).filter(_ => _.items.length > 0)
return rows return rows
}, },
useContainFit () { attachmentsDimensionalScore () {
return this.$store.getters.mergedConfig.useContainFit return this.rows.reduce((acc, row) => {
let size = 0
if (row.minimal) {
size += 1 / 8
} else if (row.audio) {
size += 1 / 4
} else {
size += 1 / (row.items.length + 0.6)
}
return acc + size
}, 0)
},
tooManyAttachments () {
if (this.editable || this.size === 'small') {
return false
} else if (this.size === 'hide') {
return this.attachments.length > 8
} else {
return this.attachmentsDimensionalScore > 1
}
} }
}, },
methods: { methods: {
onNaturalSizeLoad (id, size) { onNaturalSizeLoad ({ id, width, height }) {
this.$set(this.sizes, id, size) set(this.sizes, id, { width, height })
}, },
rowStyle (itemsPerRow) { rowStyle (row) {
return { 'padding-bottom': `${(100 / (itemsPerRow + 0.6))}%` } if (row.audio) {
return { 'padding-bottom': '25%' } // fixed reduced height for audio
} else if (!row.minimal && !row.grid) {
return { 'padding-bottom': `${(100 / (row.items.length + 0.6))}%` }
}
}, },
itemStyle (id, row) { itemStyle (id, row) {
const total = sumBy(row, item => this.getAspectRatio(item.id)) const total = sumBy(row, item => this.getAspectRatio(item.id))
@ -46,6 +101,16 @@ const Gallery = {
getAspectRatio (id) { getAspectRatio (id) {
const size = this.sizes[id] const size = this.sizes[id]
return size ? size.width / size.height : 1 return size ? size.width / size.height : 1
},
toggleHidingLong (event) {
this.hidingLong = event
},
openGallery () {
this.$store.dispatch('setMedia', this.attachments)
this.$store.dispatch('setCurrentMedia', this.attachments[0])
},
onMedia () {
this.$store.dispatch('setMedia', this.attachments)
} }
} }
} }

View file

@ -1,29 +1,86 @@
<template> <template>
<div <div
ref="galleryContainer" ref="galleryContainer"
style="width: 100%;" class="Gallery"
:class="{ '-long': tooManyAttachments && hidingLong }"
>
<div class="gallery-rows">
<div
v-for="(row, rowIndex) in rows"
:key="rowIndex"
class="gallery-row"
:style="rowStyle(row)"
:class="{ '-audio': row.audio, '-minimal': row.minimal, '-grid': grid }"
> >
<div <div
v-for="(row, index) in rows" class="gallery-row-inner"
:key="index" :class="{ '-grid': grid }"
class="gallery-row"
:style="rowStyle(row.length)"
:class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }"
> >
<div class="gallery-row-inner"> <Attachment
<attachment v-for="(attachment, attachmentIndex) in row.items"
v-for="attachment in row"
:key="attachment.id" :key="attachment.id"
:set-media="setMedia" class="gallery-item"
:nsfw="nsfw" :nsfw="nsfw"
:attachment="attachment" :attachment="attachment"
:allow-play="false" :size="size"
:natural-size-load="onNaturalSizeLoad.bind(null, attachment.id)" :editable="editable"
:style="itemStyle(attachment.id, row)" :remove="removeAttachment"
:shift-up="!(attachmentIndex === 0 && rowIndex === 0) && shiftUpAttachment"
:shift-dn="!(attachmentIndex === row.items.length - 1 && rowIndex === rows.length - 1) && shiftDnAttachment"
:edit="editAttachment"
:description="descriptions && descriptions[attachment.id]"
:hide-description="size === 'small' || tooManyAttachments && hidingLong"
:style="itemStyle(attachment.id, row.items)"
@setMedia="onMedia"
@naturalSizeLoad="onNaturalSizeLoad"
/> />
</div> </div>
</div> </div>
</div> </div>
<div
v-if="tooManyAttachments"
class="many-attachments"
>
<div class="many-attachments-text">
{{ $t("status.many_attachments", { number: attachments.length }) }}
</div>
<div class="many-attachments-buttons">
<span
v-if="!hidingLong"
class="many-attachments-button"
>
<button
class="button-unstyled -link"
@click="toggleHidingLong(true)"
>
{{ $t("status.collapse_attachments") }}
</button>
</span>
<span
v-if="hidingLong"
class="many-attachments-button"
>
<button
class="button-unstyled -link"
@click="toggleHidingLong(false)"
>
{{ $t("status.show_all_attachments") }}
</button>
</span>
<span
v-if="hidingLong"
class="many-attachments-button"
>
<button
class="button-unstyled -link"
@click="openGallery"
>
{{ $t("status.open_gallery") }}
</button>
</span>
</div>
</div>
</div>
</template> </template>
<script src='./gallery.js'></script> <script src='./gallery.js'></script>
@ -31,12 +88,66 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.gallery-row { .Gallery {
.gallery-rows {
display: flex;
flex-direction: column;
}
.gallery-row {
position: relative; position: relative;
height: 0; height: 0;
width: 100%; width: 100%;
flex-grow: 1; flex-grow: 1;
&:not(:first-child) {
margin-top: 0.5em; margin-top: 0.5em;
}
}
&.-long {
.gallery-rows {
max-height: 25em;
overflow: hidden;
mask:
linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
linear-gradient(to top, white, white);
/* Autoprefixed seem to ignore this one, and also syntax is different */
-webkit-mask-composite: xor;
mask-composite: exclude;
}
}
.many-attachments-text {
text-align: center;
line-height: 2;
}
.many-attachments-buttons {
display: flex;
}
.many-attachments-button {
display: flex;
flex: 1;
justify-content: center;
line-height: 2;
button {
padding: 0 2em;
}
}
.gallery-row {
&.-grid,
&.-minimal {
height: auto;
.gallery-row-inner {
position: relative;
}
}
}
.gallery-row-inner { .gallery-row-inner {
position: absolute; position: absolute;
@ -48,9 +159,24 @@
flex-direction: row; flex-direction: row;
flex-wrap: nowrap; flex-wrap: nowrap;
align-content: stretch; align-content: stretch;
&.-grid {
width: 100%;
height: auto;
position: relative;
display: grid;
grid-column-gap: 0.5em;
grid-row-gap: 0.5em;
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
.gallery-item {
margin: 0;
height: 200px;
}
}
} }
.gallery-row-inner .attachment { .gallery-item {
margin: 0 0.5em 0 0; margin: 0 0.5em 0 0;
flex-grow: 1; flex-grow: 1;
height: 100%; height: 100%;
@ -61,32 +187,5 @@
margin: 0; margin: 0;
} }
} }
.image-attachment {
width: 100%;
height: 100%;
}
.video-container {
height: 100%;
}
&.contain-fit {
img,
video,
canvas {
object-fit: contain;
height: 100%;
}
}
&.cover-fit {
img,
video,
canvas {
object-fit: cover;
}
}
} }
</style> </style>

View file

@ -117,7 +117,7 @@ const ImageCropper = {
const fileInput = this.$refs.input const fileInput = this.$refs.input
fileInput.addEventListener('change', this.readFile) fileInput.addEventListener('change', this.readFile)
}, },
beforeDestroy: function () { beforeUnmount: function () {
// remove the event listeners // remove the event listeners
const trigger = this.getTriggerDOM() const trigger = this.getTriggerDOM()
if (trigger) { if (trigger) {

View file

@ -15,24 +15,9 @@ const Importer = {
type: Function, type: Function,
required: true required: true
}, },
submitButtonLabel: { submitButtonLabel: { type: String },
type: String, successMessage: { type: String },
default () { errorMessage: { type: String }
return this.$t('importer.submit')
}
},
successMessage: {
type: String,
default () {
return this.$t('importer.success')
}
},
errorMessage: {
type: String,
default () {
return this.$t('importer.error')
}
}
}, },
data () { data () {
return { return {

View file

@ -18,21 +18,31 @@
class="btn button-default" class="btn button-default"
@click="submit" @click="submit"
> >
{{ submitButtonLabel }} {{ submitButtonLabel || $t('importer.submit') }}
</button> </button>
<div v-if="success"> <div v-if="success">
<button
class="button-unstyled"
@click="dismiss"
>
<FAIcon <FAIcon
icon="times" icon="times"
@click="dismiss"
/> />
<p>{{ successMessage }}</p> </button>
{{ ' ' }}
<span>{{ successMessage || $t('importer.success') }}</span>
</div> </div>
<div v-else-if="error"> <div v-else-if="error">
<button
class="button-unstyled"
@click="dismiss"
>
<FAIcon <FAIcon
icon="times" icon="times"
@click="dismiss"
/> />
<p>{{ errorMessage }}</p> </button>
{{ ' ' }}
<span>{{ errorMessage || $t('importer.error') }}</span>
</div> </div>
</div> </div>
</template> </template>

View file

@ -1,4 +1,5 @@
import Notifications from '../notifications/notifications.vue' import Notifications from '../notifications/notifications.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
const tabModeDict = { const tabModeDict = {
mentions: ['mention'], mentions: ['mention'],
@ -20,7 +21,8 @@ const Interactions = {
} }
}, },
components: { components: {
Notifications Notifications,
TabSwitcher
} }
} }

View file

@ -3,6 +3,7 @@
<label for="interface-language-switcher"> <label for="interface-language-switcher">
{{ $t('settings.interfaceLanguage') }} {{ $t('settings.interfaceLanguage') }}
</label> </label>
{{ ' ' }}
<Select <Select
id="interface-language-switcher" id="interface-language-switcher"
v-model="language" v-model="language"

View file

@ -76,11 +76,15 @@
> >
<div class="alert error"> <div class="alert error">
{{ error }} {{ error }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon <FAIcon
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="times" icon="times"
@click="clearError"
/> />
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,24 +1,46 @@
import StillImage from '../still-image/still-image.vue' import StillImage from '../still-image/still-image.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue' import VideoAttachment from '../video_attachment/video_attachment.vue'
import Modal from '../modal/modal.vue' import Modal from '../modal/modal.vue'
import fileTypeService from '../../services/file_type/file_type.service.js' import PinchZoom from '../pinch_zoom/pinch_zoom.vue'
import SwipeClick from '../swipe_click/swipe_click.vue'
import GestureService from '../../services/gesture_service/gesture_service' import GestureService from '../../services/gesture_service/gesture_service'
import Flash from 'src/components/flash/flash.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faChevronLeft, faChevronLeft,
faChevronRight faChevronRight,
faCircleNotch,
faTimes
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add( library.add(
faChevronLeft, faChevronLeft,
faChevronRight faChevronRight,
faCircleNotch,
faTimes
) )
const MediaModal = { const MediaModal = {
components: { components: {
StillImage, StillImage,
VideoAttachment, VideoAttachment,
Modal PinchZoom,
SwipeClick,
Modal,
Flash
},
data () {
return {
loading: false,
swipeDirection: GestureService.DIRECTION_LEFT,
swipeThreshold: () => {
const considerableMoveRatio = 1 / 4
return window.innerWidth * considerableMoveRatio
},
pinchZoomMinScale: 1,
pinchZoomScaleResetLimit: 1.2
}
}, },
computed: { computed: {
showing () { showing () {
@ -27,6 +49,9 @@ const MediaModal = {
media () { media () {
return this.$store.state.mediaViewer.media return this.$store.state.mediaViewer.media
}, },
description () {
return this.currentMedia.description
},
currentIndex () { currentIndex () {
return this.$store.state.mediaViewer.currentIndex return this.$store.state.mediaViewer.currentIndex
}, },
@ -37,43 +62,62 @@ const MediaModal = {
return this.media.length > 1 return this.media.length > 1
}, },
type () { type () {
return this.currentMedia ? fileTypeService.fileType(this.currentMedia.mimetype) : null return this.currentMedia ? this.getType(this.currentMedia) : null
} }
}, },
created () {
this.mediaSwipeGestureRight = GestureService.swipeGesture(
GestureService.DIRECTION_RIGHT,
this.goPrev,
50
)
this.mediaSwipeGestureLeft = GestureService.swipeGesture(
GestureService.DIRECTION_LEFT,
this.goNext,
50
)
},
methods: { methods: {
mediaTouchStart (e) { getType (media) {
GestureService.beginSwipe(e, this.mediaSwipeGestureRight) return fileTypeService.fileType(media.mimetype)
GestureService.beginSwipe(e, this.mediaSwipeGestureLeft)
},
mediaTouchMove (e) {
GestureService.updateSwipe(e, this.mediaSwipeGestureRight)
GestureService.updateSwipe(e, this.mediaSwipeGestureLeft)
}, },
hide () { hide () {
// HACK: Closing immediately via a touch will cause the click
// to be processed on the content below the overlay
const transitionTime = 100 // ms
setTimeout(() => {
this.$store.dispatch('closeMediaViewer') this.$store.dispatch('closeMediaViewer')
}, transitionTime)
},
hideIfNotSwiped (event) {
// If we have swiped over SwipeClick, do not trigger hide
const comp = this.$refs.swipeClick
if (!comp) {
this.hide()
} else {
comp.$gesture.click(event)
}
}, },
goPrev () { goPrev () {
if (this.canNavigate) { if (this.canNavigate) {
const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1) const prevIndex = this.currentIndex === 0 ? this.media.length - 1 : (this.currentIndex - 1)
this.$store.dispatch('setCurrent', this.media[prevIndex]) const newMedia = this.media[prevIndex]
if (this.getType(newMedia) === 'image') {
this.loading = true
}
this.$store.dispatch('setCurrentMedia', newMedia)
} }
}, },
goNext () { goNext () {
if (this.canNavigate) { if (this.canNavigate) {
const nextIndex = this.currentIndex === this.media.length - 1 ? 0 : (this.currentIndex + 1) const nextIndex = this.currentIndex === this.media.length - 1 ? 0 : (this.currentIndex + 1)
this.$store.dispatch('setCurrent', this.media[nextIndex]) const newMedia = this.media[nextIndex]
if (this.getType(newMedia) === 'image') {
this.loading = true
}
this.$store.dispatch('setCurrentMedia', newMedia)
}
},
onImageLoaded () {
this.loading = false
},
handleSwipePreview (offsets) {
this.$refs.pinchZoom.setTransform({ scale: 1, x: offsets[0], y: 0 })
},
handleSwipeEnd (sign) {
this.$refs.pinchZoom.setTransform({ scale: 1, x: 0, y: 0 })
if (sign > 0) {
this.goNext()
} else if (sign < 0) {
this.goPrev()
} }
}, },
handleKeyupEvent (e) { handleKeyupEvent (e) {
@ -98,7 +142,7 @@ const MediaModal = {
document.addEventListener('keyup', this.handleKeyupEvent) document.addEventListener('keyup', this.handleKeyupEvent)
document.addEventListener('keydown', this.handleKeydownEvent) document.addEventListener('keydown', this.handleKeydownEvent)
}, },
destroyed () { unmounted () {
window.removeEventListener('popstate', this.hide) window.removeEventListener('popstate', this.hide)
document.removeEventListener('keyup', this.handleKeyupEvent) document.removeEventListener('keyup', this.handleKeyupEvent)
document.removeEventListener('keydown', this.handleKeydownEvent) document.removeEventListener('keydown', this.handleKeydownEvent)

View file

@ -2,18 +2,38 @@
<Modal <Modal
v-if="showing" v-if="showing"
class="media-modal-view" class="media-modal-view"
@backdropClicked="hide" @backdropClicked="hideIfNotSwiped"
>
<SwipeClick
v-if="type === 'image'"
ref="swipeClick"
class="modal-image-container"
:direction="swipeDirection"
:threshold="swipeThreshold"
@preview-requested="handleSwipePreview"
@swipe-finished="handleSwipeEnd"
@swipeless-clicked="hide"
>
<PinchZoom
ref="pinchZoom"
class="modal-image-container-inner"
selector=".modal-image"
reach-min-scale-strategy="reset"
stop-propagate-handled="stop-propgate-handled"
:allow-pan-min-scale="pinchZoomMinScale"
:min-scale="pinchZoomMinScale"
:reset-to-min-scale-limit="pinchZoomScaleResetLimit"
> >
<img <img
v-if="type === 'image'" :class="{ loading }"
class="modal-image" class="modal-image"
:src="currentMedia.url" :src="currentMedia.url"
:alt="currentMedia.description" :alt="currentMedia.description"
:title="currentMedia.description" :title="currentMedia.description"
@touchstart.stop="mediaTouchStart" @load="onImageLoaded"
@touchmove.stop="mediaTouchMove"
@click="hide"
> >
</PinchZoom>
</SwipeClick>
<VideoAttachment <VideoAttachment
v-if="type === 'video'" v-if="type === 'video'"
class="modal-image" class="modal-image"
@ -28,38 +48,84 @@
:title="currentMedia.description" :title="currentMedia.description"
controls controls
/> />
<Flash
v-if="type === 'flash'"
class="modal-image"
:src="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
/>
<button <button
v-if="canNavigate" v-if="canNavigate"
:title="$t('media_modal.previous')" :title="$t('media_modal.previous')"
class="modal-view-button-arrow modal-view-button-arrow--prev" class="modal-view-button modal-view-button-arrow modal-view-button-arrow--prev"
@click.stop.prevent="goPrev" @click.stop.prevent="goPrev"
> >
<FAIcon <FAIcon
class="arrow-icon" class="button-icon arrow-icon"
icon="chevron-left" icon="chevron-left"
/> />
</button> </button>
<button <button
v-if="canNavigate" v-if="canNavigate"
:title="$t('media_modal.next')" :title="$t('media_modal.next')"
class="modal-view-button-arrow modal-view-button-arrow--next" class="modal-view-button modal-view-button-arrow modal-view-button-arrow--next"
@click.stop.prevent="goNext" @click.stop.prevent="goNext"
> >
<FAIcon <FAIcon
class="arrow-icon" class="button-icon arrow-icon"
icon="chevron-right" icon="chevron-right"
/> />
</button> </button>
<button
class="modal-view-button modal-view-button-hide"
:title="$t('media_modal.hide')"
@click.stop.prevent="hide"
>
<FAIcon
class="button-icon"
icon="times"
/>
</button>
<span
v-if="description"
class="description"
>
{{ description }}
</span>
<span
class="counter"
>
{{ $tc('media_modal.counter', currentIndex + 1, { current: currentIndex + 1, total: media.length }) }}
</span>
<span
v-if="loading"
class="loading-spinner"
>
<FAIcon
spin
icon="circle-notch"
size="5x"
/>
</span>
</Modal> </Modal>
</template> </template>
<script src="./media_modal.js"></script> <script src="./media_modal.js"></script>
<style lang="scss"> <style lang="scss">
$modal-view-button-icon-height: 3em;
$modal-view-button-icon-half-height: calc(#{$modal-view-button-icon-height} / 2);
$modal-view-button-icon-width: 3em;
$modal-view-button-icon-margin: 0.5em;
.modal-view.media-modal-view { .modal-view.media-modal-view {
z-index: 1001; z-index: 1001;
flex-direction: column;
.modal-view-button-arrow { .modal-view-button-arrow,
.modal-view-button-hide {
opacity: 0.75; opacity: 0.75;
&:focus, &:focus,
@ -67,36 +133,91 @@
outline: none; outline: none;
box-shadow: none; box-shadow: none;
} }
&:hover { &:hover {
opacity: 1; opacity: 1;
} }
} }
overflow: hidden;
} }
@keyframes media-fadein { .media-modal-view {
@keyframes media-fadein {
from { from {
opacity: 0; opacity: 0;
} }
to { to {
opacity: 1; opacity: 1;
} }
} }
.modal-image { .modal-image-container {
max-width: 90%; display: flex;
max-height: 90%; overflow: hidden;
box-shadow: 0px 5px 15px 0 rgba(0, 0, 0, 0.5); align-items: center;
flex-direction: column;
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
flex-grow: 1;
justify-content: center;
&-inner {
width: 100%;
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
.description,
.counter {
/* Hardcoded since background is also hardcoded */
color: white;
margin-top: 1em;
text-shadow: 0 0 10px black, 0 0 10px black;
padding: 0.2em 2em;
}
.description {
flex: 0 0 auto;
overflow-y: auto;
min-height: 1em;
max-width: 500px;
max-height: 9.5em;
word-break: break-all;
}
.modal-image {
max-width: 100%;
max-height: 100%;
image-orientation: from-image; // NOTE: only FF supports this image-orientation: from-image; // NOTE: only FF supports this
animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein; animation: 0.1s cubic-bezier(0.7, 0, 1, 0.6) media-fadein;
}
.modal-view-button-arrow { &.loading {
opacity: 0.5;
}
}
.loading-spinner {
width: 100%;
height: 100%;
position: absolute; position: absolute;
display: block; pointer-events: none;
top: 50%; display: flex;
margin-top: -50px; justify-content: center;
width: 70px; align-items: center;
height: 100px;
svg {
color: white;
}
}
.modal-view-button {
border: 0; border: 0;
padding: 0; padding: 0;
opacity: 0; opacity: 0;
@ -106,14 +227,33 @@
overflow: visible; overflow: visible;
cursor: pointer; cursor: pointer;
transition: opacity 333ms cubic-bezier(.4,0,.22,1); transition: opacity 333ms cubic-bezier(.4,0,.22,1);
height: $modal-view-button-icon-height;
width: $modal-view-button-icon-width;
.button-icon {
position: absolute;
height: $modal-view-button-icon-height;
width: $modal-view-button-icon-width;
font-size: 14px;
line-height: $modal-view-button-icon-height;
color: #FFF;
text-align: center;
background-color: rgba(0,0,0,.3);
}
}
.modal-view-button-arrow {
position: absolute;
display: block;
top: 50%;
margin-top: $modal-view-button-icon-half-height;
width: $modal-view-button-icon-width;
height: $modal-view-button-icon-height;
.arrow-icon { .arrow-icon {
position: absolute; position: absolute;
top: 35px; top: 0;
height: 30px; line-height: $modal-view-button-icon-height;
width: 32px;
font-size: 14px;
line-height: 30px;
color: #FFF; color: #FFF;
text-align: center; text-align: center;
background-color: rgba(0,0,0,.3); background-color: rgba(0,0,0,.3);
@ -122,14 +262,25 @@
&--prev { &--prev {
left: 0; left: 0;
.arrow-icon { .arrow-icon {
left: 6px; left: $modal-view-button-icon-margin;
} }
} }
&--next { &--next {
right: 0; right: 0;
.arrow-icon { .arrow-icon {
right: 6px; right: $modal-view-button-icon-margin;
}
}
}
.modal-view-button-hide {
position: absolute;
top: 0;
right: 0;
.button-icon {
top: $modal-view-button-icon-margin;
right: $modal-view-button-icon-margin;
} }
} }
} }

View file

@ -1,6 +1,7 @@
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters, mapState } from 'vuex' import { mapGetters, mapState } from 'vuex'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import UserAvatar from '../user_avatar/user_avatar.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faAt faAt
@ -12,6 +13,9 @@ library.add(
const MentionLink = { const MentionLink = {
name: 'MentionLink', name: 'MentionLink',
components: {
UserAvatar
},
props: { props: {
url: { url: {
required: true, required: true,
@ -50,6 +54,10 @@ const MentionLink = {
userName () { userName () {
return this.user && this.userNameFullUi.split('@')[0] return this.user && this.userNameFullUi.split('@')[0]
}, },
serverName () {
// XXX assumed that domain does not contain @
return this.user && (this.userNameFullUi.split('@')[1] || this.$store.getters.instanceDomain)
},
userNameFull () { userNameFull () {
return this.user && this.user.screen_name return this.user && this.user.screen_name
}, },
@ -79,12 +87,43 @@ const MentionLink = {
classnames () { classnames () {
return [ return [
{ {
'-you': this.isYou, '-you': this.isYou && this.shouldBoldenYou,
'-highlighted': this.highlight '-highlighted': this.highlight
}, },
this.highlightType this.highlightType
] ]
}, },
useAtIcon () {
return this.mergedConfig.useAtIcon
},
isRemote () {
return this.userName !== this.userNameFull
},
shouldShowFullUserName () {
const conf = this.mergedConfig.mentionLinkDisplay
if (conf === 'short') {
return false
} else if (conf === 'full') {
return true
} else { // full_for_remote
return this.isRemote
}
},
shouldShowTooltip () {
return this.mergedConfig.mentionLinkShowTooltip && this.mergedConfig.mentionLinkDisplay === 'short' && this.isRemote
},
shouldShowAvatar () {
return this.mergedConfig.mentionLinkShowAvatar
},
shouldShowYous () {
return this.mergedConfig.mentionLinkShowYous
},
shouldBoldenYou () {
return this.mergedConfig.mentionLinkBoldenYou
},
shouldFadeDomain () {
return this.mergedConfig.mentionLinkFadeDomain
},
...mapGetters(['mergedConfig']), ...mapGetters(['mergedConfig']),
...mapState({ ...mapState({
currentUser: state => state.users.currentUser currentUser: state => state.users.currentUser

View file

@ -1,15 +1,27 @@
@import '../../_variables.scss';
.MentionLink { .MentionLink {
position: relative; position: relative;
white-space: normal; white-space: normal;
display: inline-block; display: inline;
color: var(--link); color: var(--link);
word-break: normal;
& .new, & .new,
& .original { & .original {
display: inline-block; display: inline;
border-radius: 2px; border-radius: 2px;
} }
.mention-avatar {
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
width: 1.5em;
height: 1.5em;
vertical-align: middle;
user-select: none;
margin-right: 0.2em;
}
.full { .full {
position: absolute; position: absolute;
display: inline-block; display: inline-block;
@ -27,7 +39,8 @@
user-select: all; user-select: all;
} }
.short { & .short.-with-tooltip,
& .you {
user-select: none; user-select: none;
} }
@ -36,6 +49,10 @@
white-space: nowrap; white-space: nowrap;
} }
.shortName {
white-space: normal;
}
.new { .new {
&.-you { &.-you {
& .shortName, & .shortName,
@ -48,7 +65,6 @@
color: var(--link); color: var(--link);
opacity: 0.8; opacity: 0.8;
display: inline-block; display: inline-block;
height: 50%;
line-height: 1; line-height: 1;
padding: 0 0.1em; padding: 0 0.1em;
vertical-align: -25%; vertical-align: -25%;
@ -56,7 +72,7 @@
} }
&.-striped { &.-striped {
& .userName, & .shortName,
& .full { & .full {
background-image: background-image:
repeating-linear-gradient( repeating-linear-gradient(
@ -70,14 +86,14 @@
} }
&.-solid { &.-solid {
& .userName, & .shortName,
& .full { & .full {
background-image: linear-gradient(var(--____highlight-tintColor2), var(--____highlight-tintColor2)); background-image: linear-gradient(var(--____highlight-tintColor2), var(--____highlight-tintColor2));
} }
} }
&.-side { &.-side {
& .userName, & .shortName,
& .userNameFull { & .userNameFull {
box-shadow: 0 -5px 3px -4px inset var(--____highlight-solidColor); box-shadow: 0 -5px 3px -4px inset var(--____highlight-solidColor);
} }
@ -88,4 +104,12 @@
opacity: 1; opacity: 1;
pointer-events: initial; pointer-events: initial;
} }
.serverName.-faded {
color: var(--faintLink, $fallback--link);
}
.full .-faded {
color: var(--faint, $fallback--faint);
}
} }

View file

@ -9,9 +9,7 @@
class="original" class="original"
target="_blank" target="_blank"
v-html="content" v-html="content"
/> /><!-- eslint-enable vue/no-v-html --><span
<!-- eslint-enable vue/no-v-html -->
<span
v-if="user" v-if="user"
class="new" class="new"
:style="style" :style="style"
@ -19,33 +17,56 @@
> >
<a <a
class="short button-unstyled" class="short button-unstyled"
:class="{ '-with-tooltip': shouldShowTooltip }"
:href="url" :href="url"
@click.prevent="onClick" @click.prevent="onClick"
> >
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<FAIcon <UserAvatar
v-if="shouldShowAvatar"
class="mention-avatar"
:user="user"
/><span
class="shortName"
><FAIcon
v-if="useAtIcon"
size="sm" size="sm"
icon="at" icon="at"
class="at" class="at"
/><span class="shortName"><span />{{ !useAtIcon ? '@' : '' }}<span
class="userName" class="userName"
v-html="userName" v-html="userName"
/></span> /><span
v-if="shouldShowFullUserName"
class="serverName"
:class="{ '-faded': shouldFadeDomain }"
v-html="'@' + serverName"
/>
</span>
<span <span
v-if="isYou" v-if="isYou && shouldShowYous"
class="you" :class="{ '-you': shouldBoldenYou }"
>{{ $t('status.you') }}</span> > {{ ' ' + $t('status.you') }}</span>
<!-- eslint-enable vue/no-v-html --> <!-- eslint-enable vue/no-v-html -->
</a> </a><span
<span v-if="shouldShowTooltip"
v-if="userName !== userNameFull"
class="full popover-default" class="full popover-default"
:class="[highlightType]" :class="[highlightType]"
> >
<span <span
class="userNameFull" class="userNameFull"
v-text="'@' + userNameFull" >
<!-- eslint-disable vue/no-v-html -->
@<span
class="userName"
v-html="userName"
/><span
class="serverName"
:class="{ '-faded': shouldFadeDomain }"
v-html="'@' + serverName"
/> />
<!-- eslint-enable vue/no-v-html -->
</span>
</span> </span>
</span> </span>
</span> </span>

View file

@ -1,11 +1,13 @@
.MentionsLine { .MentionsLine {
word-break: break-all;
.mention-link:not(:first-child)::before {
content: ' ';
}
.showMoreLess { .showMoreLess {
margin-left: 0.5em;
white-space: normal; white-space: normal;
color: var(--link); color: var(--link);
} }
.fullExtraMentions,
.mention-link:not(:last-child) {
margin-right: 0.25em;
}
} }

View file

@ -6,7 +6,6 @@
class="mention-link" class="mention-link"
:content="mention.content" :content="mention.content"
:url="mention.url" :url="mention.url"
:first-mention="false"
/><span /><span
v-if="manyMentions" v-if="manyMentions"
class="extraMentions" class="extraMentions"
@ -21,7 +20,6 @@
class="mention-link" class="mention-link"
:content="mention.content" :content="mention.content"
:url="mention.url" :url="mention.url"
:first-mention="false"
/> />
</span><button </span><button
v-if="!expanded" v-if="!expanded"

View file

@ -56,11 +56,15 @@
> >
<div class="alert error"> <div class="alert error">
{{ error }} {{ error }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon <FAIcon
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="times" icon="times"
@click="clearError"
/> />
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -58,12 +58,16 @@
> >
<div class="alert error"> <div class="alert error">
{{ error }} {{ error }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon <FAIcon
size="lg" size="lg"
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="times" icon="times"
@click="clearError"
/> />
</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -99,6 +99,9 @@
width: 100%; width: 100%;
position: fixed; position: fixed;
box-sizing: border-box; box-sizing: border-box;
a {
color: var(--topBarLink, $fallback--link);
}
} }
.mobile-inner-nav { .mobile-inner-nav {

View file

@ -29,7 +29,7 @@ const MobilePostStatusButton = {
} }
window.addEventListener('resize', this.handleOSK) window.addEventListener('resize', this.handleOSK)
}, },
destroyed () { unmounted () {
if (this.autohideFloatingPostButton) { if (this.autohideFloatingPostButton) {
this.deactivateFloatingPostButtonAutohide() this.deactivateFloatingPostButtonAutohide()
} }

View file

@ -1,13 +1,12 @@
<template> <template>
<div v-if="isLoggedIn">
<button <button
class="button-default new-status-button" v-if="isLoggedIn"
class="MobilePostButton button-default new-status-button"
:class="{ 'hidden': isHidden, 'always-show': isPersistent }" :class="{ 'hidden': isHidden, 'always-show': isPersistent }"
@click="openPostForm" @click="openPostForm"
> >
<FAIcon icon="pen" /> <FAIcon icon="pen" />
</button> </button>
</div>
</template> </template>
<script src="./mobile_post_status_button.js"></script> <script src="./mobile_post_status_button.js"></script>
@ -15,7 +14,8 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.new-status-button { .MobilePostButton {
&.button-default {
width: 5em; width: 5em;
height: 5em; height: 5em;
border-radius: 100%; border-radius: 100%;
@ -34,6 +34,7 @@
transition: 0.35s transform; transition: 0.35s transform;
transition-timing-function: cubic-bezier(0, 1, 0.5, 1); transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
}
&.hidden { &.hidden {
transform: translateY(150%); transform: translateY(150%);

View file

@ -132,7 +132,7 @@
</button> </button>
</template> </template>
</Popover> </Popover>
<portal to="modal"> <teleport to="#modal">
<DialogModal <DialogModal
v-if="showDeleteUserDialog" v-if="showDeleteUserDialog"
:on-cancel="deleteUserDialog.bind(this, false)" :on-cancel="deleteUserDialog.bind(this, false)"
@ -156,7 +156,7 @@
</button> </button>
</template> </template>
</DialogModal> </DialogModal>
</portal> </teleport>
</div> </div>
</template> </template>

View file

@ -2,8 +2,19 @@
// TODO Copypaste from Status, should unify it somehow // TODO Copypaste from Status, should unify it somehow
.Notification { .Notification {
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
word-wrap: break-word;
word-break: break-word;
--emoji-size: 14px; --emoji-size: 14px;
&:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
--_still-image-label-visibility: hidden;
}
&.-muted { &.-muted {
padding: 0.25em 0.6em; padding: 0.25em 0.6em;
height: 1.2em; height: 1.2em;

View file

@ -1,6 +1,7 @@
<template> <template>
<Status <Status
v-if="notification.type === 'mention'" v-if="notification.type === 'mention'"
class="Notification"
:compact="true" :compact="true"
:statusoid="notification.status" :statusoid="notification.status"
/> />
@ -32,7 +33,7 @@
> >
<a <a
class="avatar-container" class="avatar-container"
:href="notification.from_profile.statusnet_profile_url" :href="$router.resolve(userProfileLink).href"
@click.stop.prevent.capture="toggleUserExpanded" @click.stop.prevent.capture="toggleUserExpanded"
> >
<UserAvatar <UserAvatar
@ -64,12 +65,16 @@
v-else v-else
class="username" class="username"
:title="'@'+notification.from_profile.screen_name_ui" :title="'@'+notification.from_profile.screen_name_ui"
>{{ notification.from_profile.name }}</span> >
{{ notification.from_profile.name }}
</span>
{{ ' ' }}
<span v-if="notification.type === 'like'"> <span v-if="notification.type === 'like'">
<FAIcon <FAIcon
class="type-icon" class="type-icon"
icon="star" icon="star"
/> />
{{ ' ' }}
<small>{{ $t('notifications.favorited_you') }}</small> <small>{{ $t('notifications.favorited_you') }}</small>
</span> </span>
<span v-if="notification.type === 'repeat'"> <span v-if="notification.type === 'repeat'">
@ -78,6 +83,7 @@
icon="retweet" icon="retweet"
:title="$t('tool_tip.repeat')" :title="$t('tool_tip.repeat')"
/> />
{{ ' ' }}
<small>{{ $t('notifications.repeated_you') }}</small> <small>{{ $t('notifications.repeated_you') }}</small>
</span> </span>
<span v-if="notification.type === 'follow'"> <span v-if="notification.type === 'follow'">
@ -85,6 +91,7 @@
class="type-icon" class="type-icon"
icon="user-plus" icon="user-plus"
/> />
{{ ' ' }}
<small>{{ $t('notifications.followed_you') }}</small> <small>{{ $t('notifications.followed_you') }}</small>
</span> </span>
<span v-if="notification.type === 'follow_request'"> <span v-if="notification.type === 'follow_request'">
@ -92,6 +99,7 @@
class="type-icon" class="type-icon"
icon="user" icon="user"
/> />
{{ ' ' }}
<small>{{ $t('notifications.follow_request') }}</small> <small>{{ $t('notifications.follow_request') }}</small>
</span> </span>
<span v-if="notification.type === 'move'"> <span v-if="notification.type === 'move'">
@ -99,13 +107,17 @@
class="type-icon" class="type-icon"
icon="suitcase-rolling" icon="suitcase-rolling"
/> />
{{ ' ' }}
<small>{{ $t('notifications.migrated_to') }}</small> <small>{{ $t('notifications.migrated_to') }}</small>
</span> </span>
<span v-if="notification.type === 'pleroma:emoji_reaction'"> <span v-if="notification.type === 'pleroma:emoji_reaction'">
<small> <small>
<i18n path="notifications.reacted_with"> <i18n-t
scope="global"
keypath="notifications.reacted_with"
>
<span class="emoji-reaction-emoji">{{ notification.emoji }}</span> <span class="emoji-reaction-emoji">{{ notification.emoji }}</span>
</i18n> </i18n-t>
</small> </small>
</span> </span>
</div> </div>
@ -159,19 +171,27 @@
<div <div
v-if="notification.type === 'follow_request'" v-if="notification.type === 'follow_request'"
style="white-space: nowrap;" style="white-space: nowrap;"
>
<button
class="button-unstyled"
:title="$t('tool_tip.accept_follow_request')"
@click="approveUser()"
> >
<FAIcon <FAIcon
icon="check" icon="check"
class="fa-scale-110 fa-old-padding follow-request-accept" class="fa-scale-110 fa-old-padding follow-request-accept"
:title="$t('tool_tip.accept_follow_request')"
@click="approveUser()"
/> />
</button>
<button
class="button-unstyled"
:title="$t('tool_tip.reject_follow_request')"
@click="denyUser()"
>
<FAIcon <FAIcon
icon="times" icon="times"
class="fa-scale-110 fa-old-padding follow-request-reject" class="fa-scale-110 fa-old-padding follow-request-reject"
:title="$t('tool_tip.reject_follow_request')"
@click="denyUser()"
/> />
</button>
</div> </div>
</div> </div>
<div <div
@ -183,8 +203,9 @@
</router-link> </router-link>
</div> </div>
<template v-else> <template v-else>
<status-content <StatusContent
class="faint" class="faint"
:compact="true"
:status="notification.action" :status="notification.action"
/> />
</template> </template>

View file

@ -37,11 +37,6 @@
.notification { .notification {
box-sizing: border-box; box-sizing: border-box;
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
word-wrap: break-word;
word-break: break-word;
&:hover .animated.Avatar { &:hover .animated.Avatar {
canvas { canvas {
@ -69,8 +64,6 @@
} }
.follow-request-accept { .follow-request-accept {
cursor: pointer;
&:hover { &:hover {
color: $fallback--text; color: $fallback--text;
color: var(--text, $fallback--text); color: var(--text, $fallback--text);
@ -78,8 +71,6 @@
} }
.follow-request-reject { .follow-request-reject {
cursor: pointer;
&:hover { &:hover {
color: $fallback--cRed; color: $fallback--cRed;
color: var(--cRed, $fallback--cRed); color: var(--cRed, $fallback--cRed);

View file

@ -11,21 +11,21 @@
</label> </label>
<Checkbox <Checkbox
v-if="typeof fallback !== 'undefined'" v-if="typeof fallback !== 'undefined'"
:checked="present" :model-value="present"
:disabled="disabled" :disabled="disabled"
class="opt" class="opt"
@change="$emit('input', !present ? fallback : undefined)" @update:modelValue="$emit('update:modelValue', !present ? fallback : undefined)"
/> />
<input <input
:id="name" :id="name"
class="input-number" class="input-number"
type="number" type="number"
:value="value || fallback" :value="modelValue || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
max="1" max="1"
min="0" min="0"
step=".05" step=".05"
@input="$emit('input', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
> >
</div> </div>
</template> </template>
@ -37,11 +37,12 @@ export default {
Checkbox Checkbox
}, },
props: [ props: [
'name', 'value', 'fallback', 'disabled' 'name', 'modelValue', 'fallback', 'disabled'
], ],
emits: ['update:modelValue'],
computed: { computed: {
present () { present () {
return typeof this.value !== 'undefined' return typeof this.modelValue !== 'undefined'
} }
} }
} }

View file

@ -0,0 +1,13 @@
import PinchZoom from '@kazvmoe-infra/pinch-zoom-element'
export default {
methods: {
setTransform ({ scale, x, y }) {
this.$el.setTransform({ scale, x, y })
}
},
created () {
// Make lint happy
(() => PinchZoom)()
}
}

View file

@ -0,0 +1,11 @@
<template>
<pinch-zoom
class="pinch-zoom-parent"
v-bind="$attrs"
v-on="$listeners"
>
<slot />
</pinch-zoom>
</template>
<script src="./pinch_zoom.js"></script>

View file

@ -21,7 +21,7 @@ export default {
} }
this.$store.dispatch('trackPoll', this.pollId) this.$store.dispatch('trackPoll', this.pollId)
}, },
destroyed () { unmounted () {
this.$store.dispatch('untrackPoll', this.pollId) this.$store.dispatch('untrackPoll', this.pollId)
}, },
computed: { computed: {

View file

@ -71,13 +71,18 @@
{{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}&nbsp;·&nbsp; {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}&nbsp;·&nbsp;
</template> </template>
</div> </div>
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'"> <span>
<i18n-t
scope="global"
:keypath="expired ? 'polls.expired' : 'polls.expires_in'"
>
<Timeago <Timeago
:time="expiresAt" :time="expiresAt"
:auto-update="60" :auto-update="60"
:now-threshold="0" :now-threshold="0"
/> />
</i18n> </i18n-t>
</span>
</div> </div>
</div> </div>
</template> </template>

View file

@ -72,6 +72,7 @@
:max="maxExpirationInCurrentUnit" :max="maxExpirationInCurrentUnit"
@change="expiryAmountChange" @change="expiryAmountChange"
> >
{{ ' ' }}
<Select <Select
v-model="expiryUnit" v-model="expiryUnit"
unstyled="true" unstyled="true"

View file

@ -178,7 +178,7 @@ const Popover = {
created () { created () {
document.addEventListener('click', this.onClickOutside) document.addEventListener('click', this.onClickOutside)
}, },
destroyed () { unmounted () {
document.removeEventListener('click', this.onClickOutside) document.removeEventListener('click', this.onClickOutside)
this.hidePopover() this.hidePopover()
} }

View file

@ -33,7 +33,7 @@
@import '../../_variables.scss'; @import '../../_variables.scss';
.popover-trigger-button { .popover-trigger-button {
display: block; display: inline-block;
} }
.popover { .popover {
@ -149,5 +149,30 @@
} }
} }
.button-default.dropdown-item {
&,
i[class*=icon-] {
color: $fallback--text;
color: var(--btnText, $fallback--text);
}
&:active {
background-color: $fallback--lightBg;
background-color: var(--selectedMenuPopover, $fallback--lightBg);
color: $fallback--link;
color: var(--selectedMenuPopoverText, $fallback--link);
}
&:disabled {
color: $fallback--text;
color: var(--btnDisabledText, $fallback--text);
}
&.toggled {
color: $fallback--text;
color: var(--btnToggledText, $fallback--text);
}
}
} }
</style> </style>

View file

@ -4,6 +4,7 @@ import ScopeSelector from '../scope_selector/scope_selector.vue'
import EmojiInput from '../emoji_input/emoji_input.vue' import EmojiInput from '../emoji_input/emoji_input.vue'
import PollForm from '../poll/poll_form.vue' import PollForm from '../poll/poll_form.vue'
import Attachment from '../attachment/attachment.vue' import Attachment from '../attachment/attachment.vue'
import Gallery from 'src/components/gallery/gallery.vue'
import StatusContent from '../status_content/status_content.vue' import StatusContent from '../status_content/status_content.vue'
import fileTypeService from '../../services/file_type/file_type.service.js' import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js' import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@ -77,6 +78,12 @@ const PostStatusForm = {
'emojiPickerPlacement', 'emojiPickerPlacement',
'optimisticPosting' 'optimisticPosting'
], ],
emits: [
'posted',
'resize',
'mediaplay',
'mediapause'
],
components: { components: {
MediaUpload, MediaUpload,
EmojiInput, EmojiInput,
@ -85,7 +92,8 @@ const PostStatusForm = {
Checkbox, Checkbox,
Select, Select,
Attachment, Attachment,
StatusContent StatusContent,
Gallery
}, },
mounted () { mounted () {
this.updateIdempotencyKey() this.updateIdempotencyKey()
@ -388,6 +396,21 @@ const PostStatusForm = {
this.newStatus.files.splice(index, 1) this.newStatus.files.splice(index, 1)
this.$emit('resize') this.$emit('resize')
}, },
editAttachment (fileInfo, newText) {
this.newStatus.mediaDescriptions[fileInfo.id] = newText
},
shiftUpMediaFile (fileInfo) {
const { files } = this.newStatus
const index = this.newStatus.files.indexOf(fileInfo)
files.splice(index, 1)
files.splice(index - 1, 0, fileInfo)
},
shiftDnMediaFile (fileInfo) {
const { files } = this.newStatus
const index = this.newStatus.files.indexOf(fileInfo)
files.splice(index, 1)
files.splice(index + 1, 0, fileInfo)
},
uploadFailed (errString, templateArgs) { uploadFailed (errString, templateArgs) {
templateArgs = templateArgs || {} templateArgs = templateArgs || {}
this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs) this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)

View file

@ -18,11 +18,12 @@
<FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" /> <FAIcon :icon="uploadFileLimitReached ? 'ban' : 'upload'" />
</div> </div>
<div class="form-group"> <div class="form-group">
<i18n <i18n-t
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning" v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private' && !disableLockWarning"
path="post_status.account_not_locked_warning" keypath="post_status.account_not_locked_warning"
tag="p" tag="p"
class="visibility-notice" class="visibility-notice"
scope="global"
> >
<button <button
class="button-unstyled -link" class="button-unstyled -link"
@ -30,7 +31,7 @@
> >
{{ $t('post_status.account_not_locked_warning_link') }} {{ $t('post_status.account_not_locked_warning_link') }}
</button> </button>
</i18n> </i18n-t>
<p <p
v-if="!hideScopeNotice && newStatus.visibility === 'public'" v-if="!hideScopeNotice && newStatus.visibility === 'public'"
class="visibility-notice notice-dismissible" class="visibility-notice notice-dismissible"
@ -281,38 +282,32 @@
class="alert error" class="alert error"
> >
Error: {{ error }} Error: {{ error }}
<button
class="button-unstyled"
@click="clearError"
>
<FAIcon <FAIcon
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
icon="times" icon="times"
@click="clearError"
/> />
</div>
<div class="attachments">
<div
v-for="file in newStatus.files"
:key="file.url"
class="media-upload-wrapper"
>
<button
class="button-unstyled hider"
@click="removeMediaFile(file)"
>
<FAIcon icon="times" />
</button> </button>
<attachment </div>
:attachment="file" <gallery
v-if="newStatus.files && newStatus.files.length > 0"
class="attachments"
:grid="true"
:nsfw="false"
:attachments="newStatus.files"
:descriptions="newStatus.mediaDescriptions"
:set-media="() => $store.dispatch('setMedia', newStatus.files)" :set-media="() => $store.dispatch('setMedia', newStatus.files)"
size="small" :editable="true"
allow-play="false" :edit-attachment="editAttachment"
:remove-attachment="removeMediaFile"
:shift-up-attachment="newStatus.files.length > 1 && shiftUpMediaFile"
:shift-dn-attachment="newStatus.files.length > 1 && shiftDnMediaFile"
@play="$emit('mediaplay', attachment.id)"
@pause="$emit('mediapause', attachment.id)"
/> />
<input
v-model="newStatus.mediaDescriptions[file.id]"
type="text"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
>
</div>
</div>
<div <div
v-if="newStatus.files.length > 0 && !disableSensitivityCheckbox" v-if="newStatus.files.length > 0 && !disableSensitivityCheckbox"
class="upload_settings" class="upload_settings"
@ -330,26 +325,13 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.tribute-container {
ul {
padding: 0px;
li {
display: flex;
align-items: center;
}
}
img {
padding: 3px;
width: 16px;
height: 16px;
border-radius: $fallback--avatarAltRadius;
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
}
}
.post-status-form { .post-status-form {
position: relative; position: relative;
.attachments {
margin-bottom: 0.5em;
}
.form-bottom { .form-bottom {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -507,15 +489,6 @@
flex-direction: column; flex-direction: column;
} }
.attachments .media-upload-wrapper {
position: relative;
.attachment {
margin: 0;
padding: 0;
}
}
.btn { .btn {
cursor: pointer; cursor: pointer;
} }
@ -616,11 +589,4 @@
border: 2px dashed var(--text, $fallback--text); border: 2px dashed var(--text, $fallback--text);
} }
} }
// todo: unify with attachment.vue (otherwise the uploaded images are not minified unless a status with an attachment was displayed before)
img.media-upload, .media-upload-container > video {
line-height: 0;
max-height: 200px;
max-width: 100%;
}
</style> </style>

View file

@ -9,7 +9,7 @@ const PublicAndExternalTimeline = {
created () { created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' }) this.$store.dispatch('startFetchingTimeline', { timeline: 'publicAndExternal' })
}, },
destroyed () { unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal') this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
} }
} }

View file

@ -9,7 +9,7 @@ const PublicTimeline = {
created () { created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'public' }) this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
}, },
destroyed () { unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'public') this.$store.dispatch('stopFetchingTimeline', 'public')
} }

View file

@ -15,7 +15,7 @@
class="opt" class="opt"
type="checkbox" type="checkbox"
:checked="present" :checked="present"
@input="$emit('input', !present ? fallback : undefined)" @change="$emit('update:modelValue', !present ? fallback : undefined)"
> >
<label <label
v-if="typeof fallback !== 'undefined'" v-if="typeof fallback !== 'undefined'"
@ -26,23 +26,23 @@
:id="name" :id="name"
class="input-number" class="input-number"
type="range" type="range"
:value="value || fallback" :value="modelValue || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
:max="max || hardMax || 100" :max="max || hardMax || 100"
:min="min || hardMin || 0" :min="min || hardMin || 0"
:step="step || 1" :step="step || 1"
@input="$emit('input', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
> >
<input <input
:id="name" :id="name"
class="input-number" class="input-number"
type="number" type="number"
:value="value || fallback" :value="modelValue || fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
:max="hardMax" :max="hardMax"
:min="hardMin" :min="hardMin"
:step="step || 1" :step="step || 1"
@input="$emit('input', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
> >
</div> </div>
</template> </template>
@ -50,11 +50,12 @@
<script> <script>
export default { export default {
props: [ props: [
'name', 'value', 'fallback', 'disabled', 'label', 'max', 'min', 'step', 'hardMin', 'hardMax' 'name', 'modelValue', 'fallback', 'disabled', 'label', 'max', 'min', 'step', 'hardMin', 'hardMax'
], ],
emits: ['update:modelValue'],
computed: { computed: {
present () { present () {
return typeof this.value !== 'undefined' return typeof this.modelValue !== 'undefined'
} }
} }
} }

View file

@ -1,9 +1,9 @@
import { validationMixin } from 'vuelidate' import useVuelidate from '@vuelidate/core'
import { required, requiredIf, sameAs } from 'vuelidate/lib/validators' import { required, requiredIf, sameAs } from '@vuelidate/validators'
import { mapActions, mapState } from 'vuex' import { mapActions, mapState } from 'vuex'
const registration = { const registration = {
mixins: [validationMixin], setup () { return { v$: useVuelidate() } },
data: () => ({ data: () => ({
user: { user: {
email: '', email: '',
@ -24,7 +24,7 @@ const registration = {
password: { required }, password: { required },
confirm: { confirm: {
required, required,
sameAsPassword: sameAs('password') sameAs: sameAs(this.user.password)
}, },
reason: { required: requiredIf(() => this.accountApprovalRequired) } reason: { required: requiredIf(() => this.accountApprovalRequired) }
} }
@ -65,9 +65,9 @@ const registration = {
this.user.captcha_token = this.captcha.token this.user.captcha_token = this.captcha.token
this.user.captcha_answer_data = this.captcha.answer_data this.user.captcha_answer_data = this.captcha.answer_data
this.$v.$touch() this.v$.$touch()
if (!this.$v.$invalid) { if (!this.v$.$invalid) {
try { try {
await this.signUp(this.user) await this.signUp(this.user)
this.$router.push({ name: 'friends' }) this.$router.push({ name: 'friends' })

View file

@ -12,7 +12,7 @@
<div class="text-fields"> <div class="text-fields">
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': $v.user.username.$error }" :class="{ 'form-group--error': v$.user.username.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -20,18 +20,18 @@
>{{ $t('login.username') }}</label> >{{ $t('login.username') }}</label>
<input <input
id="sign-up-username" id="sign-up-username"
v-model.trim="$v.user.username.$model" v-model.trim="v$.user.username.$model"
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
:placeholder="$t('registration.username_placeholder')" :placeholder="$t('registration.username_placeholder')"
> >
</div> </div>
<div <div
v-if="$v.user.username.$dirty" v-if="v$.user.username.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!$v.user.username.required"> <li v-if="!v$.user.username.required">
<span>{{ $t('registration.validations.username_required') }}</span> <span>{{ $t('registration.validations.username_required') }}</span>
</li> </li>
</ul> </ul>
@ -39,7 +39,7 @@
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': $v.user.fullname.$error }" :class="{ 'form-group--error': v$.user.fullname.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -47,18 +47,18 @@
>{{ $t('registration.fullname') }}</label> >{{ $t('registration.fullname') }}</label>
<input <input
id="sign-up-fullname" id="sign-up-fullname"
v-model.trim="$v.user.fullname.$model" v-model.trim="v$.user.fullname.$model"
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
:placeholder="$t('registration.fullname_placeholder')" :placeholder="$t('registration.fullname_placeholder')"
> >
</div> </div>
<div <div
v-if="$v.user.fullname.$dirty" v-if="v$.user.fullname.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!$v.user.fullname.required"> <li v-if="!v$.user.fullname.required">
<span>{{ $t('registration.validations.fullname_required') }}</span> <span>{{ $t('registration.validations.fullname_required') }}</span>
</li> </li>
</ul> </ul>
@ -66,7 +66,7 @@
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': $v.user.email.$error }" :class="{ 'form-group--error': v$.user.email.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -74,18 +74,18 @@
>{{ $t('registration.email') }}</label> >{{ $t('registration.email') }}</label>
<input <input
id="email" id="email"
v-model="$v.user.email.$model" v-model="v$.user.email.$model"
:disabled="isPending" :disabled="isPending"
class="form-control" class="form-control"
type="email" type="email"
> >
</div> </div>
<div <div
v-if="$v.user.email.$dirty" v-if="v$.user.email.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!$v.user.email.required"> <li v-if="!v$.user.email.required">
<span>{{ $t('registration.validations.email_required') }}</span> <span>{{ $t('registration.validations.email_required') }}</span>
</li> </li>
</ul> </ul>
@ -107,7 +107,7 @@
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': $v.user.password.$error }" :class="{ 'form-group--error': v$.user.password.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -122,11 +122,11 @@
> >
</div> </div>
<div <div
v-if="$v.user.password.$dirty" v-if="v$.user.password.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!$v.user.password.required"> <li v-if="!v$.user.password.required">
<span>{{ $t('registration.validations.password_required') }}</span> <span>{{ $t('registration.validations.password_required') }}</span>
</li> </li>
</ul> </ul>
@ -134,7 +134,7 @@
<div <div
class="form-group" class="form-group"
:class="{ 'form-group--error': $v.user.confirm.$error }" :class="{ 'form-group--error': v$.user.confirm.$error }"
> >
<label <label
class="form--label" class="form--label"
@ -149,14 +149,14 @@
> >
</div> </div>
<div <div
v-if="$v.user.confirm.$dirty" v-if="v$.user.confirm.$dirty"
class="form-error" class="form-error"
> >
<ul> <ul>
<li v-if="!$v.user.confirm.required"> <li v-if="!v$.user.confirm.required">
<span>{{ $t('registration.validations.password_confirmation_required') }}</span> <span>{{ $t('registration.validations.password_confirmation_required') }}</span>
</li> </li>
<li v-if="!$v.user.confirm.sameAsPassword"> <li v-if="!v$.user.confirm.sameAsPassword">
<span>{{ $t('registration.validations.password_confirmation_match') }}</span> <span>{{ $t('registration.validations.password_confirmation_match') }}</span>
</li> </li>
</ul> </ul>

View file

@ -1,4 +1,3 @@
import Vue from 'vue'
import { unescape, flattenDeep } from 'lodash' import { unescape, flattenDeep } from 'lodash'
import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js' import { getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/utility.service.js'
import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js' import { convertHtmlToTree } from 'src/services/html_converter/html_tree_converter.service.js'
@ -27,8 +26,12 @@ import './rich_content.scss'
* *
* Apart from that one small hiccup with emit in render this _should_ be vue3-ready * Apart from that one small hiccup with emit in render this _should_ be vue3-ready
*/ */
export default Vue.component('RichContent', { export default {
name: 'RichContent', name: 'RichContent',
components: {
MentionsLine,
HashtagLink
},
props: { props: {
// Original html content // Original html content
html: { html: {
@ -58,7 +61,7 @@ export default Vue.component('RichContent', {
} }
}, },
// NEVER EVER TOUCH DATA INSIDE RENDER // NEVER EVER TOUCH DATA INSIDE RENDER
render (h) { render () {
// Pre-process HTML // Pre-process HTML
const { newHtml: html } = preProcessPerLine(this.html, this.greentext) const { newHtml: html } = preProcessPerLine(this.html, this.greentext)
let currentMentions = null // Current chain of mentions, we group all mentions together let currentMentions = null // Current chain of mentions, we group all mentions together
@ -76,18 +79,19 @@ export default Vue.component('RichContent', {
const renderImage = (tag) => { const renderImage = (tag) => {
return <StillImage return <StillImage
{...{ attrs: getAttrs(tag) }} {...getAttrs(tag)}
class="img" class="img"
/> />
} }
const renderHashtag = (attrs, children, encounteredTextReverse) => { const renderHashtag = (attrs, children, encounteredTextReverse) => {
const linkData = getLinkData(attrs, children, tagsIndex++) const { index, ...linkData } = getLinkData(attrs, children, tagsIndex++)
writtenTags.push(linkData) writtenTags.push(linkData)
if (!encounteredTextReverse) { if (!encounteredTextReverse) {
lastTags.push(linkData) lastTags.push(linkData)
} }
return <HashtagLink {...{ props: linkData }}/> const { url, tag, content } = linkData
return <HashtagLink url={url} tag={tag} content={content}/>
} }
const renderMention = (attrs, children) => { const renderMention = (attrs, children) => {
@ -120,7 +124,8 @@ export default Vue.component('RichContent', {
// don't include spaces when processing mentions - we'll include them // don't include spaces when processing mentions - we'll include them
// in MentionsLine // in MentionsLine
lastSpacing = item lastSpacing = item
return currentMentions !== null ? item.trim() : item // Don't remove last space in a container (fixes poast mentions)
return (index !== array.length - 1) && (currentMentions !== null) ? item.trim() : item
} }
currentMentions = null currentMentions = null
@ -221,7 +226,7 @@ export default Vue.component('RichContent', {
attrs.target = '_blank' attrs.target = '_blank'
const newChildren = [...children].reverse().map(processItemReverse).reverse() const newChildren = [...children].reverse().map(processItemReverse).reverse()
return <a {...{ attrs }}> return <a {...attrs}>
{ newChildren } { newChildren }
</a> </a>
} }
@ -234,7 +239,7 @@ export default Vue.component('RichContent', {
const newChildren = Array.isArray(children) const newChildren = Array.isArray(children)
? [...children].reverse().map(processItemReverse).reverse() ? [...children].reverse().map(processItemReverse).reverse()
: children : children
return <Tag {...{ attrs: getAttrs(opener) }}> return <Tag {...getAttrs(opener)}>
{ newChildren } { newChildren }
</Tag> </Tag>
} else { } else {
@ -265,7 +270,7 @@ export default Vue.component('RichContent', {
return result return result
} }
}) }
const getLinkData = (attrs, children, index) => { const getLinkData = (attrs, children, index) => {
const stripTags = (item) => { const stripTags = (item) => {

View file

@ -16,6 +16,7 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</button> </button>
{{ ' ' }}
<button <button
v-if="showPrivate" v-if="showPrivate"
class="button-unstyled scope" class="button-unstyled scope"
@ -29,6 +30,7 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</button> </button>
{{ ' ' }}
<button <button
v-if="showUnlisted" v-if="showUnlisted"
class="button-unstyled scope" class="button-unstyled scope"
@ -42,6 +44,7 @@
class="fa-scale-110 fa-old-padding" class="fa-scale-110 fa-old-padding"
/> />
</button> </button>
{{ ' ' }}
<button <button
v-if="showPublic" v-if="showPublic"
class="button-unstyled scope" class="button-unstyled scope"

View file

@ -1,6 +1,7 @@
import FollowCard from '../follow_card/follow_card.vue' import FollowCard from '../follow_card/follow_card.vue'
import Conversation from '../conversation/conversation.vue' import Conversation from '../conversation/conversation.vue'
import Status from '../status/status.vue' import Status from '../status/status.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import map from 'lodash/map' import map from 'lodash/map'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -17,7 +18,8 @@ const Search = {
components: { components: {
FollowCard, FollowCard,
Conversation, Conversation,
Status Status,
TabSwitcher
}, },
props: [ props: [
'query' 'query'

View file

@ -8,12 +8,9 @@ library.add(
) )
export default { export default {
model: { emits: ['update:modelValue'],
prop: 'value',
event: 'change'
},
props: [ props: [
'value', 'modelValue',
'disabled', 'disabled',
'unstyled', 'unstyled',
'kind' 'kind'

View file

@ -1,4 +1,3 @@
<template> <template>
<label <label
class="Select input" class="Select input"
@ -6,11 +5,12 @@
> >
<select <select
:disabled="disabled" :disabled="disabled"
:value="value" :value="modelValue"
@change="$emit('change', $event.target.value)" @change="$emit('update:modelValue', $event.target.value)"
> >
<slot /> <slot />
</select> </select>
{{ ' ' }}
<FAIcon <FAIcon
class="select-down-icon" class="select-down-icon"
icon="chevron-down" icon="chevron-down"
@ -23,7 +23,8 @@
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.Select { /* TODO fix order of styles */
label.Select {
padding: 0; padding: 0;
select { select {
@ -51,6 +52,7 @@
bottom: 0; bottom: 0;
right: 5px; right: 5px;
height: 100%; height: 100%;
width: 0.875em;
color: $fallback--text; color: $fallback--text;
color: var(--inputText, $fallback--text); color: var(--inputText, $fallback--text);
line-height: 28px; line-height: 28px;

View file

@ -6,9 +6,9 @@
> >
<div class="selectable-list-checkbox-wrapper"> <div class="selectable-list-checkbox-wrapper">
<Checkbox <Checkbox
:checked="allSelected" :model-value="allSelected"
:indeterminate="someSelected" :indeterminate="someSelected"
@change="toggleAll" @update:model-value="toggleAll"
> >
{{ $t('selectable_list.select_all') }} {{ $t('selectable_list.select_all') }}
</Checkbox> </Checkbox>
@ -31,8 +31,8 @@
> >
<div class="selectable-list-checkbox-wrapper"> <div class="selectable-list-checkbox-wrapper">
<Checkbox <Checkbox
:checked="isSelected(item)" :model-value="isSelected(item)"
@change="checked => toggle(checked, item)" @update:model-value="checked => toggle(checked, item)"
/> />
</div> </div>
<slot <slot

View file

@ -1,14 +1,17 @@
import { get, set } from 'lodash' import { get, set } from 'lodash'
import Checkbox from 'src/components/checkbox/checkbox.vue' import Checkbox from 'src/components/checkbox/checkbox.vue'
import ModifiedIndicator from './modified_indicator.vue' import ModifiedIndicator from './modified_indicator.vue'
import ServerSideIndicator from './server_side_indicator.vue'
export default { export default {
components: { components: {
Checkbox, Checkbox,
ModifiedIndicator ModifiedIndicator,
ServerSideIndicator
}, },
props: [ props: [
'path', 'path',
'disabled' 'disabled',
'expert'
], ],
computed: { computed: {
pathDefault () { pathDefault () {
@ -26,8 +29,14 @@ export default {
defaultState () { defaultState () {
return get(this.$parent, this.pathDefault) return get(this.$parent, this.pathDefault)
}, },
isServerSide () {
return this.path.startsWith('serverSide_')
},
isChanged () { isChanged () {
return this.state !== this.defaultState return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
},
matchesExpertLevel () {
return (this.expert || 0) <= this.$parent.expertLevel
} }
}, },
methods: { methods: {

View file

@ -1,11 +1,12 @@
<template> <template>
<label <label
v-if="matchesExpertLevel"
class="BooleanSetting" class="BooleanSetting"
> >
<Checkbox <Checkbox
:checked="state" :model-value="state"
:disabled="disabled" :disabled="disabled"
@change="update" @update:modelValue="update"
> >
<span <span
v-if="!!$slots.default" v-if="!!$slots.default"
@ -13,8 +14,8 @@
> >
<slot /> <slot />
</span> </span>
<ModifiedIndicator :changed="isChanged" /> {{ ' ' }}
</Checkbox> <ModifiedIndicator :changed="isChanged" /><ServerSideIndicator :server-side="isServerSide" /> </Checkbox>
</label> </label>
</template> </template>

View file

@ -1,15 +1,18 @@
import { get, set } from 'lodash' import { get, set } from 'lodash'
import Select from 'src/components/select/select.vue' import Select from 'src/components/select/select.vue'
import ModifiedIndicator from './modified_indicator.vue' import ModifiedIndicator from './modified_indicator.vue'
import ServerSideIndicator from './server_side_indicator.vue'
export default { export default {
components: { components: {
Select, Select,
ModifiedIndicator ModifiedIndicator,
ServerSideIndicator
}, },
props: [ props: [
'path', 'path',
'disabled', 'disabled',
'options' 'options',
'expert'
], ],
computed: { computed: {
pathDefault () { pathDefault () {
@ -27,8 +30,14 @@ export default {
defaultState () { defaultState () {
return get(this.$parent, this.pathDefault) return get(this.$parent, this.pathDefault)
}, },
isServerSide () {
return this.path.startsWith('serverSide_')
},
isChanged () { isChanged () {
return this.state !== this.defaultState return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
},
matchesExpertLevel () {
return (this.expert || 0) <= this.$parent.expertLevel
} }
}, },
methods: { methods: {

View file

@ -1,12 +1,14 @@
<template> <template>
<label <label
v-if="matchesExpertLevel"
class="ChoiceSetting" class="ChoiceSetting"
> >
<slot /> <slot />
{{ ' ' }}
<Select <Select
:value="state" :model-value="state"
:disabled="disabled" :disabled="disabled"
@change="update" @update:modelValue="update"
> >
<option <option
v-for="option in options" v-for="option in options"
@ -18,6 +20,7 @@
</option> </option>
</Select> </Select>
<ModifiedIndicator :changed="isChanged" /> <ModifiedIndicator :changed="isChanged" />
<ServerSideIndicator :server-side="isServerSide" />
</label> </label>
</template> </template>

View file

@ -0,0 +1,41 @@
import { get, set } from 'lodash'
import ModifiedIndicator from './modified_indicator.vue'
export default {
components: {
ModifiedIndicator
},
props: {
path: String,
disabled: Boolean,
min: Number,
expert: [Number, String]
},
computed: {
pathDefault () {
const [firstSegment, ...rest] = this.path.split('.')
return [firstSegment + 'DefaultValue', ...rest].join('.')
},
state () {
const value = get(this.$parent, this.path)
if (value === undefined) {
return this.defaultState
} else {
return value
}
},
defaultState () {
return get(this.$parent, this.pathDefault)
},
isChanged () {
return this.state !== this.defaultState
},
matchesExpertLevel () {
return (this.expert || 0) <= this.$parent.expertLevel
}
},
methods: {
update (e) {
set(this.$parent, this.path, parseInt(e.target.value))
}
}
}

View file

@ -0,0 +1,24 @@
<template>
<span
v-if="matchesExpertLevel"
class="IntegerSetting"
>
<label :for="path">
<slot />
</label>
<input
:id="path"
class="number-input"
type="number"
step="1"
:disabled="disabled"
:min="min || 0"
:value="state"
@change="update"
>
{{ ' ' }}
<ModifiedIndicator :changed="isChanged" />
</span>
</template>
<script src="./integer_setting.js"></script>

View file

@ -0,0 +1,51 @@
<template>
<span
v-if="serverSide"
class="ServerSideIndicator"
>
<Popover
trigger="hover"
>
<template v-slot:trigger>
&nbsp;
<FAIcon
icon="server"
:aria-label="$t('settings.setting_server_side')"
/>
</template>
<template v-slot:content>
<div class="serverside-tooltip">
{{ $t('settings.setting_server_side') }}
</div>
</template>
</Popover>
</span>
</template>
<script>
import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faServer } from '@fortawesome/free-solid-svg-icons'
library.add(
faServer
)
export default {
components: { Popover },
props: ['serverSide']
}
</script>
<style lang="scss">
.ServerSideIndicator {
display: inline-block;
position: relative;
.serverside-tooltip {
margin: 0.5em 1em;
min-width: 10em;
text-align: center;
}
}
</style>

View file

@ -1,4 +1,5 @@
import { defaultState as configDefaultState } from 'src/modules/config.js' import { defaultState as configDefaultState } from 'src/modules/config.js'
import { defaultState as serverSideConfigDefaultState } from 'src/modules/serverSideConfig.js'
const SharedComputedObject = () => ({ const SharedComputedObject = () => ({
user () { user () {
@ -22,6 +23,14 @@ const SharedComputedObject = () => ({
} }
}]) }])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
...Object.keys(serverSideConfigDefaultState)
.map(key => ['serverSide_' + key, {
get () { return this.$store.state.serverSideConfig[key] },
set (value) {
this.$store.dispatch('setServerSideOption', { name: key, value })
}
}])
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
// Special cases (need to transform values or perform actions first) // Special cases (need to transform values or perform actions first)
useStreamingApi: { useStreamingApi: {
get () { return this.$store.getters.mergedConfig.useStreamingApi }, get () { return this.$store.getters.mergedConfig.useStreamingApi },

View file

@ -3,6 +3,7 @@ import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue' import AsyncComponentError from 'src/components/async_component_error/async_component_error.vue'
import getResettableAsyncComponent from 'src/services/resettable_async_component.js' import getResettableAsyncComponent from 'src/services/resettable_async_component.js'
import Popover from '../popover/popover.vue' import Popover from '../popover/popover.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { import {
@ -51,11 +52,12 @@ const SettingsModal = {
components: { components: {
Modal, Modal,
Popover, Popover,
Checkbox,
SettingsModalContent: getResettableAsyncComponent( SettingsModalContent: getResettableAsyncComponent(
() => import('./settings_modal_content.vue'), () => import('./settings_modal_content.vue'),
{ {
loading: PanelLoading, loadingComponent: PanelLoading,
error: AsyncComponentError, errorComponent: AsyncComponentError,
delay: 0 delay: 0
} }
) )
@ -159,6 +161,15 @@ const SettingsModal = {
}, },
modalPeeked () { modalPeeked () {
return this.$store.state.interface.settingsModalState === 'minimized' return this.$store.state.interface.settingsModalState === 'minimized'
},
expertLevel: {
get () {
return this.$store.state.config.expertLevel > 0
},
set (value) {
console.log(value)
this.$store.dispatch('setOption', { name: 'expertLevel', value: value ? 1 : 0 })
}
} }
} }
} }

View file

@ -48,4 +48,16 @@
} }
} }
} }
.settings-footer {
display: flex;
>* {
margin-right: 0.5em;
}
.extra-content {
display: flex;
flex-grow: 1;
}
}
} }

View file

@ -11,7 +11,7 @@
{{ $t('settings.settings') }} {{ $t('settings.settings') }}
</span> </span>
<transition name="fade"> <transition name="fade">
<template v-if="currentSaveStateNotice"> <div v-if="currentSaveStateNotice">
<div <div
v-if="currentSaveStateNotice.error" v-if="currentSaveStateNotice.error"
class="alert error" class="alert error"
@ -27,7 +27,7 @@
> >
{{ $t('settings.saving_ok') }} {{ $t('settings.saving_ok') }}
</div> </div>
</template> </div>
</transition> </transition>
<button <button
class="btn button-default" class="btn button-default"
@ -53,7 +53,7 @@
<div class="panel-body"> <div class="panel-body">
<SettingsModalContent v-if="modalOpenedOnce" /> <SettingsModalContent v-if="modalOpenedOnce" />
</div> </div>
<div class="panel-footer"> <div class="panel-footer settings-footer">
<Popover <Popover
class="export" class="export"
trigger="click" trigger="click"
@ -68,6 +68,7 @@
:title="$t('general.close')" :title="$t('general.close')"
> >
<span>{{ $t("settings.file_export_import.backup_restore") }}</span> <span>{{ $t("settings.file_export_import.backup_restore") }}</span>
{{ ' ' }}
<FAIcon <FAIcon
icon="chevron-down" icon="chevron-down"
/> />
@ -108,6 +109,17 @@
</div> </div>
</template> </template>
</Popover> </Popover>
<Checkbox
:model-value="!!expertLevel"
@update:modelValue="expertLevel = Number($event)"
>
{{ $t("settings.expert_mode") }}
</Checkbox>
<span
id="unscrolled-content"
class="extra-content"
/>
</div> </div>
</div> </div>
</Modal> </Modal>

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