Compare commits

..

697 commits

Author SHA1 Message Date
Henry Jameson
db73f1a3dc Merge branch '2-10-1-fixes' into shigusegubu-themes3 2026-01-08 22:10:42 +02:00
Henry Jameson
69edded5b0 fix error when clicking mute.. item 2026-01-08 22:09:18 +02:00
Henry Jameson
1e08616b1f unescape 2026-01-08 19:14:58 +02:00
Henry Jameson
dcb7ed1b8c Merge remote-tracking branch 'origin/develop' into 2-10-1-fixes 2026-01-08 19:12:32 +02:00
HJ
aa25cd04b1 Merge branch 'biome' into 'develop'
Migration to Biome

See merge request pleroma/pleroma-fe!2193
2026-01-08 16:10:23 +00:00
Henry Jameson
42930252b1 better imports organization 2026-01-08 17:42:20 +02:00
Henry Jameson
6f5eb6c442 Merge remote-tracking branch 'origin/develop' into biome 2026-01-08 17:16:14 +02:00
HJ
e13e84e26d Merge branch 'fix-e2e' into 'develop'
proper playwright version

See merge request pleroma/pleroma-fe!2196
2026-01-08 15:15:35 +00:00
Henry Jameson
3d78a34daa chlg 2026-01-08 17:11:09 +02:00
Henry Jameson
ca10ffca8d proper playwright version 2026-01-08 17:04:15 +02:00
Henry Jameson
4b87fa083e Merge remote-tracking branch 'origin/develop' into 2-10-1-fixes 2026-01-08 16:55:02 +02:00
Henry Jameson
851c100a24 Merge remote-tracking branch 'origin/develop' into biome 2026-01-08 16:48:27 +02:00
HJ
9e980dcd87 Merge branch 'e2e-testing' into 'develop'
E2E: add user smoke tests

See merge request pleroma/pleroma-fe!2194
2026-01-08 14:36:23 +00:00
Lain Soykaf
6fc8cc32ca Test: exclude Playwright e2e specs from vitest
Vitest picks up *.spec.js under test/ by default; exclude test/e2e-playwright while preserving default excludes.
2026-01-08 15:09:41 +04:00
Lain Soykaf
b8bfd6b1a9 CI: enable per-build network for e2e
Fixes Pleroma service being unable to reach the Postgres service on runners that use legacy container links.
2026-01-08 14:35:51 +04:00
Henry Jameson
0dc8305e95 don't display muted label on profile since backend doesn't work this way
improve display logic for mute/block cards
2026-01-07 23:18:09 +02:00
Henry Jameson
949aa90faa fixed being unable to mute/unmute domains from status context menu 2026-01-07 21:09:01 +02:00
Henry Jameson
03b6178d17 fix missing string 2026-01-07 21:01:34 +02:00
Lain Soykaf
d2f528bb15 CI: run e2e job without docker-in-docker
Use GitLab services for Postgres + Pleroma and run Playwright tests directly in the Playwright image (works with gitlab-ci-local too).
2026-01-07 20:11:16 +04:00
Henry Jameson
0492a8d6a0 fix actor type not setting 2026-01-07 17:06:02 +02:00
Lain Soykaf
33c7876a8a Add changelog 2026-01-07 18:07:27 +04:00
Lain Soykaf
7f54706821 E2E: add user smoke tests
- Disable captcha + open registrations in E2E Pleroma config
- Await login after signup to avoid redirect race
- Add basic register/login/post smoke specs
- Fix failure artifact copying order
2026-01-07 11:05:10 +04:00
Lain Soykaf
99d2efac6c Reduce E2E output to Playwright logs 2026-01-07 10:00:18 +04:00
Lain Soykaf
7ffec2c324 Add docker-compose Playwright E2E stack 2026-01-07 09:44:50 +04:00
Henry Jameson
ae600da287 fix tests 2026-01-06 18:38:00 +02:00
Henry Jameson
c32dda4849 Merge branch 'biome' into shigusegubu-themes3 2026-01-06 18:27:01 +02:00
Henry Jameson
2197c030de lint 2026-01-06 18:22:32 +02:00
Henry Jameson
85c2947714 oops 2026-01-06 18:21:11 +02:00
Henry Jameson
f30537f25e changelog 2026-01-06 18:18:29 +02:00
Henry Jameson
d15642d93d Merge remote-tracking branch 'origin/develop' into biome 2026-01-06 18:17:40 +02:00
Henry Jameson
b2964612ae separate lint jobs in ci for better parallelism and display 2026-01-06 18:16:25 +02:00
Henry Jameson
e956d55219 update package json 2026-01-06 18:15:24 +02:00
Henry Jameson
04797f8bd2 manual lint 2026-01-06 18:06:23 +02:00
Henry Jameson
7f54d11834 nerf eslint to only lint vue files since vue support in biome is experimental 2026-01-06 18:04:11 +02:00
Henry Jameson
1c53ac84cc manual lint 2026-01-06 17:32:22 +02:00
Henry Jameson
1654234e32 simplified biome configuration + test globals 2026-01-06 16:35:44 +02:00
Henry Jameson
b05a501236 biome check --write 2026-01-06 16:23:17 +02:00
Henry Jameson
9262e803ec biome format --write 2026-01-06 16:22:52 +02:00
Henry Jameson
8372348148 biome lint --write 2026-01-06 16:21:06 +02:00
Henry Jameson
b9a77cc61d add biome 2026-01-06 16:21:06 +02:00
HJ
1fe112c7f3 Merge branch 'renovate/vite-6.x-lockfile' into 'develop'
Update dependency vite to v6.4.1

See merge request pleroma/pleroma-fe!2176
2026-01-06 14:04:05 +00:00
HJ
59b7da77c9 Merge branch 'renovate/eslint-plugin-vue-10.x' into 'develop'
Update dependency eslint-plugin-vue to v10.6.2

See merge request pleroma/pleroma-fe!2179
2026-01-06 14:03:01 +00:00
HJ
b33ded3a45 Merge branch 'renovate/playwright-monorepo' into 'develop'
Update dependency playwright to v1.57.0

See merge request pleroma/pleroma-fe!2180
2026-01-06 14:02:31 +00:00
HJ
46cbef8253 Merge branch 'renovate/eslint-monorepo' into 'develop'
Update dependency eslint to v9.39.2

See merge request pleroma/pleroma-fe!2181
2026-01-06 14:02:21 +00:00
HJ
308c5cd222 Merge branch 'renovate/vue-router-4.x' into 'develop'
Update dependency vue-router to v4.6.4

See merge request pleroma/pleroma-fe!2182
2026-01-06 14:02:07 +00:00
HJ
bbee089d64 Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo to v7.28.5

See merge request pleroma/pleroma-fe!2185
2026-01-06 14:01:26 +00:00
HJ
fd03a5ade3 Merge branch 'mergeback-210' into 'develop'
Release 2.10.0

See merge request pleroma/pleroma-fe!2192
2025-12-23 14:18:02 +00:00
Henry Jameson
d7b1aaa616 Release 2.10.0 2025-12-23 16:16:08 +02:00
HJ
971161a441 Merge branch 'fix-poll-expand' into 'develop'
fix poll notifications not being expandable

See merge request pleroma/pleroma-fe!2191
2025-12-23 13:46:28 +00:00
Henry Jameson
29fe616c1c changelog 2025-12-23 15:36:07 +02:00
Henry Jameson
50105a8f92 fix poll notifications not being expandable 2025-12-23 15:31:54 +02:00
HJ
fb77d084d4 Merge branch 'fix-interactionlist-popover-emoji' into 'develop'
fix incorrect emoji in post interaction lists

See merge request pleroma/pleroma-fe!2190
2025-12-22 15:24:05 +00:00
Henry Jameson
6ae05ab956 changelog 2025-12-22 17:19:42 +02:00
Henry Jameson
963c55cf71 fix incorrect emoji in post interaction lists 2025-12-22 17:18:27 +02:00
HJ
fd086febfa Merge branch 'fix-lists-edit' into 'develop'
fix list title edit not working

See merge request pleroma/pleroma-fe!2189
2025-12-22 12:21:50 +00:00
Henry Jameson
202d1618c2 fix list title edit not working 2025-12-22 14:15:28 +02:00
HJ
657de70153 Merge branch 'fix-1385' into 'develop'
Fix #1385

Closes #1385

See merge request pleroma/pleroma-fe!2188
2025-12-19 17:34:25 +00:00
Henry Jameson
c2732e3f40 changelogs + forgotten changelogs from previous MR 2025-12-19 19:29:27 +02:00
Henry Jameson
c9b47a0ca9 fix #1385 2025-12-19 19:27:33 +02:00
Henry Jameson
3aae2f33d2 hide compositing tab for anon viewers 2025-12-19 19:21:45 +02:00
HJ
aa426b3d14 Merge branch 'admin-tabs-2' into 'develop'
Most of the remaining admin tabs

See merge request pleroma/pleroma-fe!2187
2025-12-19 17:21:35 +00:00
Henry Jameson
c71a36de30 cleanup console logs (changed actual proper logs to .info()) 2025-12-19 19:13:24 +02:00
Henry Jameson
7d88140bb4 more changelogs 2025-12-19 19:10:53 +02:00
Henry Jameson
4502a6cc25 Merge branch 'admin-tabs-2' into shigusegubu-themes3 2025-12-15 22:56:19 +02:00
Henry Jameson
db7e4a3434 actual countdown 2025-12-15 22:56:04 +02:00
Henry Jameson
4edf6b03ce better styles 2025-12-15 22:39:59 +02:00
Henry Jameson
25a9033b6b better separation + style of PWA manifest 2025-12-15 22:35:35 +02:00
Henry Jameson
d26dca92e2 manifest is grouped together as a setting 2025-12-15 22:31:54 +02:00
Pleroma Renovate Bot
f97658b6d6 Update dependency eslint to v9.39.2 2025-12-13 09:04:36 +00:00
Pleroma Renovate Bot
b9e6b9b1f0 Update dependency vue-router to v4.6.4 2025-12-12 09:04:41 +00:00
Henry Jameson
37b9dc77f3 Merge branch 'admin-tabs-2' into shigusegubu-themes3 2025-12-11 21:58:41 +02:00
Henry Jameson
d2d8b0167c fix submit button again 2025-12-11 21:58:30 +02:00
Henry Jameson
30966210f4 Merge branch 'admin-tabs-2' into shigusegubu-themes3 2025-12-11 18:31:26 +02:00
Henry Jameson
3355f1d797 pleasing the linter gods 2025-12-11 18:29:55 +02:00
Henry Jameson
06608adec4 fix submit button 2025-12-11 18:27:12 +02:00
Henry Jameson
68aadcdd08 MANIFEST EDITING FUCK YEAH 2025-12-11 18:18:05 +02:00
Henry Jameson
39856570a8 Merge branch 'admin-tabs-2' into shigusegubu-themes3 2025-12-11 15:45:37 +02:00
Henry Jameson
0222d493f8 fix rates tab 2025-12-11 15:45:27 +02:00
Henry Jameson
bb802ed756 mailer styling controls added 2025-12-11 15:33:51 +02:00
Henry Jameson
db3bfb6fc3 color setting prettification 2025-12-11 15:14:32 +02:00
Henry Jameson
3ec21cb442 prettified pwa setting 2025-12-11 15:06:36 +02:00
Henry Jameson
5b143b2aea small improvement to label/desc display 2025-12-11 14:37:58 +02:00
Henry Jameson
96fc30a1b7 proxy setting fixes 2025-12-11 14:37:51 +02:00
Henry Jameson
edbf5f3276 developer tab fixes 2025-12-11 14:23:43 +02:00
Henry Jameson
189b092d2c fix style tab 2025-12-11 14:13:14 +02:00
Henry Jameson
bf75c7af85 fix follow button 2025-12-11 14:07:17 +02:00
Henry Jameson
952800410e emoji tab fixes 2025-12-11 14:05:46 +02:00
Henry Jameson
c085acd2dd fix emoji form & eliminate copypasta 2025-12-11 13:35:41 +02:00
Henry Jameson
c12a7fcf7b Merge branch 'admin-tabs-2' into shigusegubu-themes3 2025-12-10 20:37:38 +02:00
Henry Jameson
e4a33bf6d7 Merge remote-tracking branch 'origin/admin-tabs-2' into admin-tabs-2 2025-12-10 20:37:16 +02:00
Henry Jameson
625954721e fixed up unitsetting 2025-12-10 20:36:09 +02:00
Henry Jameson
76dd3540e4 more visual fixes 2025-12-10 20:24:39 +02:00
Henry Jameson
494f6b471e visual stuff again 2025-12-10 19:57:34 +02:00
Henry Jameson
1642d62b82 massive visual overhaul 2025-12-10 18:34:19 +02:00
Henry Jameson
5a6f4fb466 remove most of the stuff mentioned in internal.exs in !pleroma/4403 2025-12-10 15:23:41 +02:00
Henry Jameson
13ace1d24e remove most of the stuff mentioned in internal.exs in !pleroma/4403 2025-12-10 14:42:40 +02:00
Henry Jameson
03e9e5082f Merge branch 'admin-tabs-2' into shigusegubu-themes3 2025-12-09 13:24:57 +02:00
Henry Jameson
57dfbd8a53 rate limits page 2025-12-09 13:23:54 +02:00
Henry Jameson
b07560ca6b Merge branch 'admin-tabs-2' into shigusegubu-themes3 2025-12-08 22:44:37 +02:00
Henry Jameson
576774540f lint 2025-12-08 22:44:31 +02:00
Henry Jameson
12b98a48b2 Merge branch 'admin-tabs-2' into shigusegubu-themes3 2025-12-08 22:31:37 +02:00
Henry Jameson
caa0213ac6 changelog 2025-12-08 22:31:05 +02:00
Henry Jameson
a7fa7558b3 pretty much "the rest" 2025-12-08 22:30:27 +02:00
Henry Jameson
bdb992a8e5 lint 2025-12-08 18:17:11 +02:00
Henry Jameson
bc47bef80d branding/manifest part done 2025-12-08 18:12:21 +02:00
Henry Jameson
c4f83808b0 http tab more or less done 2025-12-08 17:09:07 +02:00
Henry Jameson
5aed9a20b8 fix listsetting & http ssl options 2025-12-08 13:50:08 +02:00
Henry Jameson
e961b6e14c small visual fixes 2025-12-08 13:49:30 +02:00
Henry Jameson
df8df5a0bf fix hard-reset not showing 2025-12-07 23:14:30 +02:00
Henry Jameson
7d0f03fdba proxy setting, init http tab, broken 2025-12-07 23:11:54 +02:00
Henry Jameson
0bc5442eb9 third pass of emoji reorganizing 2025-12-07 17:42:10 +02:00
Henry Jameson
4329502422 second pass of reorganizing emoji tab 2025-12-07 17:06:41 +02:00
Henry Jameson
2fe5efc69d first pass of reorganizing emoji tab 2025-12-07 15:43:57 +02:00
Henry Jameson
ace8295c03 style updates 2025-12-07 15:43:52 +02:00
Henry Jameson
d5c75915e6 federation tab 2025-12-07 14:01:33 +02:00
Henry Jameson
d7453c09b2 improvements to listsetting, added some localization strings 2025-12-07 14:01:09 +02:00
Henry Jameson
9c043533f2 MapSetting component 2025-12-06 14:38:53 +02:00
Henry Jameson
b0bce1bf18 proper tuple support 2025-12-06 14:38:39 +02:00
Henry Jameson
48ba3892c3 additions to auth tab 2025-12-04 17:39:57 +02:00
Henry Jameson
ac751320f4 Auth tab done 2025-12-04 17:20:11 +02:00
Henry Jameson
3fca18e248 hide list add string if not in expert mode 2025-12-04 15:47:48 +02:00
Henry Jameson
56a6a25112 Queues tab done 2025-12-04 14:37:25 +02:00
Henry Jameson
cdbf3f42b8 part of job queues tab done 2025-12-03 23:05:46 +02:00
Henry Jameson
0a9a3648d6 registrations tab update 2025-12-03 21:55:53 +02:00
Henry Jameson
5042db43ab add more :instance stuff to attachments tab 2025-12-03 20:40:49 +02:00
Henry Jameson
cae19d8a9a fix links tab, boost description font size 2025-12-03 20:28:39 +02:00
Henry Jameson
311e9d255d media proxy done + misc changes 2025-12-03 20:16:33 +02:00
Henry Jameson
3a321ca756 media proxy completeness 2025-12-03 12:31:57 +02:00
Henry Jameson
42a5da93ea links tab done (finally) 2025-12-03 12:31:57 +02:00
Henry Jameson
b7a97b8603 merge multicheckbox and list inputs 2025-12-02 20:48:13 +02:00
Henry Jameson
c93f55e8f7 list and multicheckbox initial implementation 2025-12-01 23:56:49 +02:00
Henry Jameson
4a98ec9611 minor fixes and moves 2025-12-01 20:14:01 +02:00
Henry Jameson
388ecd9a5e Merge remote-tracking branch 'origin/develop' into admin-tabs-2 2025-12-01 18:52:04 +02:00
HJ
0252d39c75 Merge branch 'settings-shuffle' into 'develop'
Settings shuffle

See merge request pleroma/pleroma-fe!2186
2025-12-01 09:01:46 +00:00
Henry Jameson
235e6bd233 Media proxy tab 2025-11-28 15:44:22 +02:00
Henry Jameson
672bedaf6d fix disabled classes 2025-11-28 15:44:03 +02:00
Henry Jameson
55b5d2c5d7 fixup! uploads tab 2025-11-28 14:00:00 +02:00
Henry Jameson
b73c9ae4e8 frontends tab proper hierarchy 2025-11-28 13:59:13 +02:00
Pleroma Renovate Bot
5718483558 Update dependency eslint-plugin-vue to v10.6.2 2025-11-28 09:04:37 +00:00
Henry Jameson
114d49b6d6 i18n 2025-11-28 02:11:28 +02:00
Henry Jameson
bc92f535de uploads tab 2025-11-28 02:02:39 +02:00
Henry Jameson
db535ae057 cleanup 2025-11-28 02:02:29 +02:00
Henry Jameson
eae09226b5 registrations tab 2025-11-27 20:28:58 +02:00
Henry Jameson
4704f57cbf Merge branch 'settings-shuffle' into shigusegubu-themes3 2025-11-27 19:34:23 +02:00
Henry Jameson
16f456eaea fun UI for profile background 2025-11-27 19:33:47 +02:00
Henry Jameson
489fb17070 Merge branch 'settings-shuffle' into admin-tabs-2 2025-11-27 18:20:58 +02:00
Henry Jameson
0d04b1c8ce fix scroll again 2025-11-27 18:20:47 +02:00
Henry Jameson
b38343705c mailer tab + beginning of monitoring tab 2025-11-27 18:18:03 +02:00
Henry Jameson
3bc8800c35 i18n 2025-11-27 12:27:38 +02:00
Henry Jameson
e4c5a88913 fix fun content not hiding properly 2025-11-27 12:27:36 +02:00
Henry Jameson
ad13f2417f fix tab cutoff 2025-11-27 12:27:33 +02:00
Henry Jameson
ea8218d855 i18n 2025-11-27 12:26:44 +02:00
Henry Jameson
ba8be56bc4 fix fun content not hiding properly 2025-11-27 12:03:15 +02:00
Henry Jameson
b1c338a976 fix tab cutoff 2025-11-27 12:03:07 +02:00
Henry Jameson
7aeeb25f00 Merge branch 'settings-shuffle' into shigusegubu-themes3 2025-11-26 18:44:09 +02:00
Henry Jameson
951dc87c09 fix vertical cutoff 2025-11-26 18:43:58 +02:00
Henry Jameson
7b02072133 clean up and update icon list 2025-11-26 13:02:39 +02:00
Pleroma Renovate Bot
2af0c83389 Update dependency playwright to v1.57.0 2025-11-26 08:51:51 +00:00
Henry Jameson
f77c2bef5f Merge branch 'settings-shuffle' into shigusegubu-themes3 2025-11-26 00:25:41 +02:00
Henry Jameson
04a21e4698 lint, ugh 2025-11-26 00:25:30 +02:00
Henry Jameson
41b6e80171 minor visual fix 2025-11-25 23:18:48 +02:00
Henry Jameson
b519c0f3aa lint 2025-11-25 23:18:10 +02:00
Henry Jameson
355a5955b3 made shadow control usable on mobile 2025-11-25 23:07:02 +02:00
Henry Jameson
dba63e6825 improved theme-related stuff on mobile somewhat (except shadow editor) 2025-11-25 22:56:14 +02:00
Henry Jameson
f24f164995 improve import/export table 2025-11-25 20:44:20 +02:00
Henry Jameson
e6bda9638b revise expert status for settings 2025-11-25 20:39:44 +02:00
Henry Jameson
f2db381817 Merge branch 'settings-shuffle' into shigusegubu-themes3 2025-11-25 20:30:02 +02:00
Henry Jameson
e01753830d better two-column layout 2025-11-25 20:29:37 +02:00
Henry Jameson
1e73c7e8cb Merge remote-tracking branch 'origin/develop' into settings-shuffle 2025-11-25 20:13:40 +02:00
Henry Jameson
94864276c1 improve settings nav on mobile 2025-11-25 20:11:08 +02:00
Henry Jameson
2a3999bf42 fix suboptions on mobile 2025-11-25 20:09:47 +02:00
Henry Jameson
171bb3b7ca changelog 2025-11-25 20:04:18 +02:00
Henry Jameson
23c5a6fab7 slightly adjusted security tab. that tab needs general overhaul 2025-11-25 20:02:00 +02:00
Henry Jameson
f7fc678c23 proper suboptions hierarchy 2025-11-25 19:51:34 +02:00
Henry Jameson
38a99b2f39 fixed incorrect headers on notifications tab 2025-11-25 19:45:10 +02:00
Henry Jameson
9fae1d086b rearrange layout tab sections 2025-11-25 19:45:01 +02:00
Henry Jameson
6f9cd347df capitalization of some sections 2025-11-25 19:37:34 +02:00
Henry Jameson
402cbf6593 move tree suboptions closer to its parent 2025-11-25 19:37:19 +02:00
Henry Jameson
452a522fa4 lint 2025-11-25 19:35:52 +02:00
Henry Jameson
1fd6584374 fix mutes-and-blocks tab 2025-11-25 19:27:10 +02:00
Henry Jameson
ce04595e36 move vertical tab switcher into helpers since it's not meant to be used elsewhere 2025-11-25 19:12:18 +02:00
Henry Jameson
2d0bd043cb remove extraneous separator line 2025-11-25 18:40:05 +02:00
Henry Jameson
1abbba698d Merge branch 'settings-shuffle' into shigusegubu-themes3 2025-11-24 20:54:21 +02:00
Henry Jameson
db73631459 somewhat better import-export tab + minor fixes 2025-11-24 20:53:01 +02:00
Henry Jameson
b3bf4fca75 better scroll + back-header on mobile 2025-11-24 20:26:13 +02:00
Henry Jameson
3716797e04 removed extraneous header from profile tab 2025-11-24 20:25:59 +02:00
Henry Jameson
7c57be22e4 working prototype 2025-11-24 20:05:38 +02:00
Henry Jameson
50ede338e7 further separation of tabs 2025-11-24 17:06:55 +02:00
Henry Jameson
b0f725671a improve scope selector 2025-11-24 16:19:23 +02:00
Henry Jameson
fba7d15a2c improve font control 2025-11-24 16:06:28 +02:00
Henry Jameson
9572b9704c fix logic not working on other tabs 2025-11-20 22:09:33 +02:00
Henry Jameson
63535b1494 less spam of events, fix nesting headers (again) 2025-11-20 21:54:52 +02:00
Henry Jameson
8b8af2889b better impl of header hiding? 2025-11-20 21:17:44 +02:00
Henry Jameson
3f4ad34377 crappy implementation of hiding extra header 2025-11-20 20:52:18 +02:00
Henry Jameson
7d1799e929 fix order of expansion, WIP hiding headers 2025-11-20 20:10:20 +02:00
Henry Jameson
8e6800fd1e level 2 collapse 2025-11-20 18:29:19 +02:00
Henry Jameson
e6f025bf6e after 9000 hours it finally works 2025-11-20 12:12:14 +02:00
Henry Jameson
5958c32acf fix turd that kept breaking UI on hot reload 2025-11-20 08:07:51 +02:00
Henry Jameson
a96f533777 vertical tab switcher initial implementation 2025-11-20 02:07:00 +02:00
Henry Jameson
a3a35e76a8 remove side-tabs from tab-switcher, splitting functionality into separate component 2025-11-20 01:24:38 +02:00
Pleroma Renovate Bot
1683d98699 Update babel monorepo to v7.28.5 2025-10-29 09:05:56 +00:00
Pleroma Renovate Bot
3601012cc2 Update dependency vite to v6.4.1 2025-10-21 09:06:08 +00:00
HJ
848f2c7ddb Merge branch 'fix-fix-broken-convos' into 'develop'
Fix fix broken convos

See merge request pleroma/pleroma-fe!2178
2025-10-15 14:01:29 +00:00
Henry Jameson
043c02ff40 simpler fix 2025-10-15 17:00:47 +03:00
Henry Jameson
661ab34889 revert vue file 2025-10-15 16:58:08 +03:00
Henry Jameson
debd3a3e7b initial nested settings impl 2025-10-15 16:53:16 +03:00
HJ
2f8ea4f3b3 Merge branch 'renovate/major-font-awesome' into 'develop'
Update Font Awesome to v7 (major)

See merge request pleroma/pleroma-fe!2162
2025-10-15 12:04:15 +00:00
HJ
30f9b84f08 Merge branch 'tusooa/everything-instance-default' into 'develop'
Make every configuration option default-overridable by instance admins

See merge request pleroma/pleroma-fe!2175
2025-10-14 22:25:29 +00:00
tusooa
dd910ff8a8
Make every configuration option default-overridable by instance admins 2025-10-14 18:17:29 -04:00
HJ
90f7dee343 Merge branch 'renovate/semver-7.x' into 'develop'
Update dependency semver to v7.7.3

See merge request pleroma/pleroma-fe!2169
2025-10-14 13:14:57 +00:00
HJ
f616c583f2 Merge branch 'renovate/eslint-plugin-vue-10.x' into 'develop'
Update dependency eslint-plugin-vue to v10.5.0

See merge request pleroma/pleroma-fe!2173
2025-10-14 13:14:37 +00:00
HJ
f0cf1da920 Merge branch 'renovate/globals-16.x-lockfile' into 'develop'
Update dependency globals to v16.4.0

See merge request pleroma/pleroma-fe!2174
2025-10-14 13:14:25 +00:00
Pleroma Renovate Bot
004bdd6b79 Update dependency globals to v16.4.0 2025-10-14 08:52:39 +00:00
Pleroma Renovate Bot
3286725510 Update dependency eslint-plugin-vue to v10.5.0 2025-10-14 08:52:04 +00:00
HJ
ce048667f5 Merge branch 'renovate/phoenix-1.x' into 'develop'
Update dependency phoenix to v1.8.1

See merge request pleroma/pleroma-fe!2168
2025-10-14 07:52:52 +00:00
HJ
bc23d46615 Merge branch 'renovate/chalk-5.x' into 'develop'
Update dependency chalk to v5.6.2

See merge request pleroma/pleroma-fe!2167
2025-10-14 07:52:30 +00:00
HJ
aa7911665b Merge branch 'renovate/vite-6.x-lockfile' into 'develop'
Update dependency vite to v6.3.6

See merge request pleroma/pleroma-fe!2170
2025-10-14 07:51:30 +00:00
HJ
7ba0b1d622 Merge branch 'renovate/vue-i18n-11.x-lockfile' into 'develop'
Update dependency vue-i18n to v11.1.12

See merge request pleroma/pleroma-fe!2171
2025-10-14 07:50:46 +00:00
HJ
be0bf5e119 Merge branch 'renovate/eslint-plugin-n-17.x' into 'develop'
Update dependency eslint-plugin-n to v17.23.1

See merge request pleroma/pleroma-fe!2172
2025-10-14 07:50:16 +00:00
Pleroma Renovate Bot
e9f1b29e1c Update dependency eslint-plugin-n to v17.23.1 2025-10-13 09:05:18 +00:00
Pleroma Renovate Bot
b4cd8d8fab Update dependency vue-i18n to v11.1.12 2025-10-12 09:07:07 +00:00
Pleroma Renovate Bot
d5723bbf34 Update dependency vite to v6.3.6 2025-10-12 09:06:37 +00:00
Pleroma Renovate Bot
d67967dc78 Update Font Awesome to v7 2025-10-11 09:06:29 +00:00
Pleroma Renovate Bot
59b65891af Update dependency semver to v7.7.3 2025-10-11 09:05:50 +00:00
Pleroma Renovate Bot
ff9127973e Update dependency phoenix to v1.8.1 2025-10-11 09:05:41 +00:00
Pleroma Renovate Bot
8cd50f6df3 Update dependency chalk to v5.6.2 2025-10-11 09:05:24 +00:00
HJ
b3b71fcf18 Merge branch 'renovate/chai-5.x' into 'develop'
Update dependency chai to v5.3.3

See merge request pleroma/pleroma-fe!2152
2025-10-08 12:09:05 +00:00
HJ
ce0921e208 Merge branch 'renovate/sinon-chai-4.x' into 'develop'
Update dependency sinon-chai to v4.0.1

See merge request pleroma/pleroma-fe!2153
2025-10-08 12:08:36 +00:00
HJ
e40e56a988 Merge branch 'renovate/eslint-monorepo' into 'develop'
Update dependency eslint to v9.37.0

See merge request pleroma/pleroma-fe!2155
2025-10-08 12:08:31 +00:00
HJ
41bcc4c93e Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo to v7.28.4

See merge request pleroma/pleroma-fe!2165
2025-10-08 12:07:33 +00:00
HJ
d2266303d1 Merge branch 'renovate/font-awesome' into 'develop'
Update dependency @fortawesome/vue-fontawesome to v3.1.2

See merge request pleroma/pleroma-fe!2166
2025-10-08 12:07:17 +00:00
Pleroma Renovate Bot
6c965789d8 Update dependency sinon-chai to v4.0.1 2025-10-07 08:53:01 +00:00
Pleroma Renovate Bot
3196eb79de Update dependency @fortawesome/vue-fontawesome to v3.1.2 2025-10-07 08:52:35 +00:00
Pleroma Renovate Bot
f3e897588b Update babel monorepo to v7.28.4 2025-10-07 08:52:15 +00:00
HJ
abe1b9c565 Merge branch 'renovate/stylelint-16.x' into 'develop'
Update dependency stylelint to v16.25.0

See merge request pleroma/pleroma-fe!2160
2025-10-07 07:36:42 +00:00
HJ
e0b76eeda6 Merge branch 'renovate/vue-monorepo' into 'develop'
Update vue monorepo to v3.5.22

See merge request pleroma/pleroma-fe!2154
2025-10-07 07:36:30 +00:00
HJ
811eb3d361 Merge branch 'renovate/sass-1.x' into 'develop'
Update dependency sass to v1.93.2

See merge request pleroma/pleroma-fe!2159
2025-10-07 07:36:01 +00:00
HJ
320a53835a Merge branch 'fix-broken-conversation' into 'develop'
Fix broken conversation

See merge request pleroma/pleroma-fe!2163
2025-10-07 06:48:19 +00:00
Pleroma User
00ba6b7c5d Fix broken conversation 2025-10-07 06:48:17 +00:00
Pleroma Renovate Bot
7a919e7c76 Update dependency stylelint to v16.25.0 2025-10-04 08:52:01 +00:00
Pleroma Renovate Bot
326d4976a3 Update dependency eslint to v9.37.0 2025-10-04 08:51:40 +00:00
Henry Jameson
15d0aa466b Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-10-02 11:39:29 +03:00
Pleroma Renovate Bot
df05a7a8a7 Update dependency sass to v1.93.2 2025-09-25 09:08:13 +00:00
Pleroma Renovate Bot
fb828b07f9 Update vue monorepo to v3.5.22 2025-09-25 09:07:21 +00:00
HJ
ed10af15e2 Merge branch 'master' into 'develop'
Mergeback to develop

See merge request pleroma/pleroma-fe!2158
2025-09-18 15:33:22 +00:00
HJ
6341a48d05 Merge branch 'release/2.9.x' into 'master'
2.9.3 into master

See merge request pleroma/pleroma-fe!2157
2025-09-18 15:32:55 +00:00
Henry Jameson
53afb86da1 2.9.3 2025-09-18 18:30:46 +03:00
HJ
aa7d5c0efd Merge branch 'fixes-291' into 'develop'
fix error when updating profile

See merge request pleroma/pleroma-fe!2156
2025-09-18 15:28:28 +00:00
Henry Jameson
a0a27410ea debug2 2025-09-18 18:19:50 +03:00
Henry Jameson
454a225a75 debug 2025-09-18 18:16:50 +03:00
Henry Jameson
8353db33ad fix error when updating profile 2025-09-15 19:44:23 +03:00
Henry Jameson
d242f45ffb Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-09-15 19:38:25 +03:00
HJ
40eb565f2a Merge branch 'master' into 'develop'
Mergeback

See merge request pleroma/pleroma-fe!2249
2025-08-31 13:06:12 +00:00
HJ
0ecbae9675 Merge branch 'release/2.9.x' into 'master'
Release/2.9.x

See merge request pleroma/pleroma-fe!2248
2025-08-31 13:02:47 +00:00
Henry Jameson
6e1c65a574 more changelog updates 2025-08-31 15:56:15 +03:00
Henry Jameson
b7c75dcba2 update 2025-08-31 15:26:30 +03:00
Henry Jameson
205e03ea15 template 2025-08-31 15:08:01 +03:00
Henry Jameson
f9848daf48 changelog + bump 2025-08-31 15:02:12 +03:00
HJ
30917ff017 Merge branch 'block-fix' into 'develop'
oops + release prep

See merge request pleroma/pleroma-fe!2247
2025-08-29 11:26:22 +00:00
Henry Jameson
f9570b132f frontend-specifics 2025-08-29 14:25:02 +03:00
Henry Jameson
473133aa02 prepare for release 2025-08-29 14:24:10 +03:00
Henry Jameson
a62ffbfbab oops
This partially reverts commit e3bfbcf0d2.
2025-08-29 14:23:16 +03:00
HJ
2551947ee6 Merge branch 'block-fix' into 'develop'
fix being unable to (un)block users in some cases

See merge request pleroma/pleroma-fe!2246
2025-08-29 10:57:43 +00:00
Henry Jameson
e3bfbcf0d2 fix being unable to (un)block users in some cases 2025-08-29 13:54:03 +03:00
HJ
683b2d7fea Merge branch 'final-fixes-frfr' into 'develop'
Final fixes frfr

See merge request pleroma/pleroma-fe!2245
2025-08-28 15:58:13 +00:00
Henry Jameson
0fe823aeec changelog + cleanup 2025-08-28 18:51:18 +03:00
Henry Jameson
b46c8358ae fix focus+hover for toggled buttons 2025-08-28 18:50:33 +03:00
Henry Jameson
65b40f8f72 don't adopt styelsheets prematurely 2025-08-28 15:37:53 +03:00
Henry Jameson
6b4057c6ab fix buttons being cut-off on mobile popovers 2025-08-28 15:16:31 +03:00
Henry Jameson
19e6390ccb Merge branch 'final-fixes' into shigusegubu-themes3 2025-08-28 00:06:54 +03:00
HJ
2a69abf374 Merge branch 'final-fixes' into 'develop'
Final fixes

Closes #1386

See merge request pleroma/pleroma-fe!2241
2025-08-27 20:25:15 +00:00
Henry Jameson
0560110868 changelog 2025-08-27 23:16:34 +03:00
Henry Jameson
3df779f02f reduce drop-shadow on usercard to avoid blinking 2025-08-27 22:47:38 +03:00
Henry Jameson
0b78c64928 fix notification dot 2025-08-27 22:46:00 +03:00
Henry Jameson
8c7da4c621 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-08-26 21:24:21 +03:00
Pleroma Renovate Bot
9b8bccd27d Update dependency chai to v5.3.3 2025-08-23 08:52:09 +00:00
HJ
2854b012c9 Merge branch 'renovate/playwright-monorepo' into 'develop'
Update dependency playwright to v1.55.0

See merge request pleroma/pleroma-fe!2232
2025-08-22 12:07:24 +00:00
HJ
82b560ccf2 Merge branch 'renovate/vue-eslint-parser-10.x' into 'develop'
Update dependency vue-eslint-parser to v10.2.0

See merge request pleroma/pleroma-fe!2185
2025-08-22 12:07:11 +00:00
HJ
288ad9dd06 Merge branch 'renovate/pinia-3.x-lockfile' into 'develop'
Update dependency pinia to v3.0.3

See merge request pleroma/pleroma-fe!2182
2025-08-22 11:51:04 +00:00
HJ
fdaaf333c5 Merge branch 'renovate/chai-5.x' into 'develop'
Update dependency chai to v5.3.2

See merge request pleroma/pleroma-fe!2231
2025-08-22 11:50:52 +00:00
HJ
2601c09660 Merge branch 'renovate/vitejs-plugin-vue-jsx-4.x-lockfile' into 'develop'
Update dependency @vitejs/plugin-vue-jsx to v4.2.0

See merge request pleroma/pleroma-fe!2221
2025-08-22 11:50:30 +00:00
Pleroma Renovate Bot
38fc7f784e Update dependency playwright to v1.55.0 2025-08-22 09:06:07 +00:00
Pleroma Renovate Bot
4c4144f1eb Update dependency @vitejs/plugin-vue-jsx to v4.2.0 2025-08-22 09:05:56 +00:00
Pleroma Renovate Bot
088be118c3 Update dependency chai to v5.3.2 2025-08-22 09:05:33 +00:00
HJ
58ce121010 Merge branch 'renovate/ruffle-rs-ruffle-0.x' into 'develop'
Update dependency @ruffle-rs/ruffle to v0.1.0-nightly.2025.6.22

See merge request pleroma/pleroma-fe!1981
2025-08-22 08:00:19 +00:00
HJ
7e04269b7e Merge branch 'renovate/eslint-plugin-import-2.x' into 'develop'
Update dependency eslint-plugin-import to v2.32.0

See merge request pleroma/pleroma-fe!2224
2025-08-22 07:38:18 +00:00
HJ
3bacacd214 Merge branch 'renovate/chai-5.x' into 'develop'
Update dependency chai to v5.3.1

See merge request pleroma/pleroma-fe!2227
2025-08-22 07:37:15 +00:00
HJ
b3f5edb913 Merge branch 'renovate/globals-16.x-lockfile' into 'develop'
Update dependency globals to v16.3.0

See merge request pleroma/pleroma-fe!2228
2025-08-22 07:37:07 +00:00
HJ
785c8d518a Merge branch 'renovate/vue-monorepo' into 'develop'
Update vue monorepo to v3.5.19

See merge request pleroma/pleroma-fe!2229
2025-08-22 07:36:57 +00:00
HJ
a5af76603f Merge branch 'renovate/phoenix-1.x' into 'develop'
Update dependency phoenix to v1.8.0

See merge request pleroma/pleroma-fe!2230
2025-08-22 07:36:50 +00:00
HJ
589737dced Merge branch 'misc-style-fixes-or-changes' into 'develop'
Misc style fixes or changes

Closes #1378

See merge request pleroma/pleroma-fe!2219
2025-08-21 14:58:26 +00:00
Henry Jameson
542db846a6 px -> em for reprööter bar 2025-08-21 17:40:42 +03:00
Henry Jameson
798178a86f lint 2025-08-21 17:36:43 +03:00
Henry Jameson
8f320faa2d lint 2025-08-21 17:33:57 +03:00
Henry Jameson
528cc9f388 fix emoji vertical alignment when using stealer 2025-08-21 17:28:18 +03:00
Henry Jameson
17c04d4d83 Merge remote-tracking branch 'origin/develop' into misc-style-fixes-or-changes
+ better comment
2025-08-21 17:15:50 +03:00
Pleroma Renovate Bot
0b9547b289 Update dependency phoenix to v1.8.0 2025-08-21 09:06:13 +00:00
Pleroma Renovate Bot
0ad18d3e5e Update vue monorepo to v3.5.19 2025-08-21 09:05:57 +00:00
HJ
0424992a35 Merge branch 'copy-emoji-one' into 'develop'
Allow copying a single emoji from a pack, copying emojis from posts, and show a popover on emoji click

See merge request pleroma/pleroma-fe!2207
2025-08-21 06:56:13 +00:00
Pleroma Renovate Bot
4ce9a011da Update dependency globals to v16.3.0 2025-08-20 09:06:26 +00:00
Pleroma Renovate Bot
91ab21b37b Update dependency eslint-plugin-import to v2.32.0 2025-08-20 09:06:11 +00:00
Pleroma Renovate Bot
b3ba505e51 Update dependency chai to v5.3.1 2025-08-20 09:05:57 +00:00
Henry Jameson
d7ab91e167 Merge branch 'copy-emoji-one' into shigusegubu-themes3 2025-08-20 07:14:19 +03:00
HJ
7678b7f597 Merge branch 'renovate/eslint-plugin-vue-10.x' into 'develop'
Update dependency eslint-plugin-vue to v10.4.0

See merge request pleroma/pleroma-fe!2226
2025-08-20 03:50:18 +00:00
HJ
d148f36474 Merge branch 'renovate/vue-babel-plugin-jsx-1.x' into 'develop'
Update dependency @vue/babel-plugin-jsx to v1.5.0

See merge request pleroma/pleroma-fe!2222
2025-08-20 03:49:50 +00:00
HJ
f252585b7f Merge branch 'renovate/chalk-5.x' into 'develop'
Update dependency chalk to v5.6.0

See merge request pleroma/pleroma-fe!2223
2025-08-20 03:49:39 +00:00
HJ
4aa27c5be0 Merge branch 'renovate/eslint-plugin-n-17.x' into 'develop'
Update dependency eslint-plugin-n to v17.21.3

See merge request pleroma/pleroma-fe!2225
2025-08-20 03:49:10 +00:00
Henry Jameson
09ca89871a Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-19 17:30:25 +03:00
Henry Jameson
bebb3fcfa6 better click handler 2025-08-19 17:28:19 +03:00
Henry Jameson
8780e0191e proper collapse 2025-08-19 16:37:50 +03:00
Henry Jameson
26a2232e18 fix incorrect collapse state 2025-08-19 16:21:44 +03:00
Henry Jameson
0d6453baec fix emoji reactions notifs being non-expandable 2025-08-19 16:21:30 +03:00
Pleroma Renovate Bot
80b0117ba2 Update dependency eslint-plugin-vue to v10.4.0 2025-08-18 08:53:12 +00:00
Pleroma Renovate Bot
7b3ef62b96 Update dependency eslint-plugin-n to v17.21.3 2025-08-17 08:53:36 +00:00
Pleroma Renovate Bot
15239cf92d Update dependency chalk to v5.6.0 2025-08-17 08:52:59 +00:00
Pleroma Renovate Bot
3ba9c01e15 Update dependency @vue/babel-plugin-jsx to v1.5.0 2025-08-16 09:06:10 +00:00
Ekaterina Vaartis
7ff72e5ae2 Correct shortcode attr, sort names instead of emoji, success info 2025-08-15 22:14:06 +03:00
HJ
48651518df Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo to v7.28.3

See merge request pleroma/pleroma-fe!2220
2025-08-15 09:38:24 +00:00
Pleroma Renovate Bot
202f5d87b3 Update babel monorepo to v7.28.3 2025-08-15 09:04:59 +00:00
Henry Jameson
6ff87af516 Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-15 10:06:31 +03:00
Henry Jameson
7b2f3b648a improve visibilty of attachment buttons 2025-08-15 10:03:34 +03:00
Henry Jameson
16ada7ec6c add padding to compact status' poll icon 2025-08-15 09:49:28 +03:00
Henry Jameson
028556f8ab remove icons from "show more" since we show attachments anyway 2025-08-15 09:48:28 +03:00
Henry Jameson
9ed2fd3c4b default to 1rem instead 14px 2025-08-15 09:41:08 +03:00
Henry Jameson
c47bfe53ff aria 2025-08-15 09:38:36 +03:00
Henry Jameson
30a668de7a Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-15 09:34:09 +03:00
Henry Jameson
35fea49bd0 reduce bookmark action button width 2025-08-15 09:29:08 +03:00
Henry Jameson
866b416dbe changelog update 2025-08-15 09:14:26 +03:00
Henry Jameson
1c0aa025a3 fix shadows on profile and add spacing between user handle and nickname 2025-08-15 09:13:53 +03:00
Henry Jameson
bfd1809a34 move expand click handler to StatusContent 2025-08-15 09:10:26 +03:00
Henry Jameson
31efcccbf6 Merge branch 'copy-emoji-one' into shigusegubu-themes3 2025-08-15 00:02:34 +03:00
HJ
96d886d213 Merge branch 'renovate/msw-2.x' into 'develop'
Update dependency msw to v2.10.5

See merge request pleroma/pleroma-fe!2217
2025-08-14 20:32:45 +00:00
HJ
1a0f4c8c34 Merge branch 'renovate/font-awesome' into 'develop'
Update dependency @fortawesome/vue-fontawesome to v3.1.1

See merge request pleroma/pleroma-fe!2218
2025-08-14 20:29:12 +00:00
Henry Jameson
6b71793b3b Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-14 23:16:01 +03:00
Henry Jameson
9b58b8c290 better paddings 2025-08-14 23:15:06 +03:00
Henry Jameson
9f481052d0 restore border for visibility notice 2025-08-14 21:03:25 +03:00
Henry Jameson
69726895b1 changelog 2025-08-14 18:19:57 +03:00
Henry Jameson
4da91e0076 Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-14 18:15:16 +03:00
Henry Jameson
e560b1d6c7 fix plasma browser? 2025-08-14 18:15:03 +03:00
Henry Jameson
52e4cfc573 Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-14 18:06:29 +03:00
Henry Jameson
c65c0afce3 fix emoji input 2025-08-14 18:06:08 +03:00
Henry Jameson
242de86c35 Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-14 17:58:17 +03:00
Henry Jameson
c7ad009496 apply shadow to everything, not just icons 2025-08-14 17:58:04 +03:00
Henry Jameson
f0250ffc52 fix edit banner button 2025-08-14 17:57:38 +03:00
Henry Jameson
7ee584c65d Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-14 17:50:15 +03:00
Henry Jameson
267692f56a improve shadows on user-card 2025-08-14 17:49:59 +03:00
Henry Jameson
d34e57bc0b Merge branch 'misc-style-fixes-or-changes' into shigusegubu-themes3 2025-08-14 17:27:41 +03:00
Henry Jameson
49a98a6fe2 bunch of px -> em, small fixes 2025-08-14 17:27:13 +03:00
Henry Jameson
4c17458e58 fix favicons 2025-08-14 17:08:52 +03:00
Henry Jameson
6399de114c fix right icons alignment 2025-08-14 16:56:59 +03:00
Henry Jameson
bfb9f6c5e2 scale avatars 2025-08-14 16:50:00 +03:00
Henry Jameson
e076403fcb em 2025-08-14 16:48:47 +03:00
Henry Jameson
97adff267f delay splash screen, only show if loading takes too long 2025-08-14 16:45:24 +03:00
Henry Jameson
5bb0424397 improve visibilty of user profile text 2025-08-14 16:17:34 +03:00
Henry Jameson
00c3719618 Merge remote-tracking branch 'origin/develop' into misc-style-fixes-or-changes 2025-08-14 16:14:41 +03:00
Henry Jameson
aa5688a43b unstyle the content type selector 2025-08-14 16:07:27 +03:00
Henry Jameson
f35633c855 remove redundand emoji button 2025-08-14 13:07:03 +03:00
Henry Jameson
6e479d246b visually combine subject and content into one input 2025-08-14 12:59:51 +03:00
Henry Jameson
0b9b7a51a6 collapse notifications and also allow expand on click anywhere 2025-08-14 12:53:19 +03:00
Henry Jameson
a6c844e522 make favs etc strip newlines so status is more visible 2025-08-14 12:31:12 +03:00
Henry Jameson
2869ab787d fix #1378 2025-08-14 12:24:09 +03:00
HJ
22a14d8985 Merge branch 'themes-3-1' into 'develop'
Themes 3.1: You can (not) overthink

See merge request pleroma/pleroma-fe!2209
2025-08-14 09:01:15 +00:00
HJ
ce5638d223 Merge branch 'renovate/vue-i18n-11.x-lockfile' into 'develop'
Update dependency vue-i18n to v11.1.11

See merge request pleroma/pleroma-fe!2215
2025-08-14 08:58:03 +00:00
Pleroma Renovate Bot
2a2ffff992 Update dependency @fortawesome/vue-fontawesome to v3.1.1 2025-08-14 08:53:12 +00:00
Pleroma Renovate Bot
0a4c867519 Update dependency msw to v2.10.5 2025-08-14 08:52:53 +00:00
HJ
4886b34c2f Merge branch 'renovate/vue-monorepo' into 'develop'
Update vue monorepo to v3.5.18

See merge request pleroma/pleroma-fe!2216
2025-08-14 08:52:24 +00:00
HJ
f6cdf06ba1 Merge branch 'renovate/vite-plugin-eslint2-5.x-lockfile' into 'develop'
Update dependency vite-plugin-eslint2 to v5.0.4

See merge request pleroma/pleroma-fe!2213
2025-08-14 08:52:03 +00:00
HJ
adbeb9d0dd Merge branch 'renovate/vite-plugin-stylelint-6.x-lockfile' into 'develop'
Update dependency vite-plugin-stylelint to v6.0.2

See merge request pleroma/pleroma-fe!2214
2025-08-14 08:51:54 +00:00
Henry Jameson
8958f70a48 fix hacks not applying immideately 2025-08-14 11:42:56 +03:00
Henry Jameson
f7fca3a1ba transparent attachment buttons 2025-08-14 10:43:37 +03:00
Henry Jameson
918fbecf3e fix button pressed state 2025-08-14 10:39:27 +03:00
Pleroma Renovate Bot
25d54cbeea Update vue monorepo to v3.5.18 2025-08-13 09:05:13 +00:00
Pleroma Renovate Bot
be3170b50b Update dependency vue-i18n to v11.1.11 2025-08-13 09:05:05 +00:00
Pleroma Renovate Bot
6f29f9e735 Update dependency vite-plugin-stylelint to v6.0.2 2025-08-12 09:06:05 +00:00
Pleroma Renovate Bot
1e4544b2ca Update dependency vite-plugin-eslint2 to v5.0.4 2025-08-12 09:05:48 +00:00
HJ
286bb6aa97 Merge branch 'renovate/msw-2.x' into 'develop'
Update dependency msw to v2.10.4

See merge request pleroma/pleroma-fe!2212
2025-08-11 19:42:42 +00:00
HJ
9d16aa5ace Merge branch 'renovate/cropperjs-2.x' into 'develop'
Update dependency cropperjs to v2.0.1

See merge request pleroma/pleroma-fe!2211
2025-08-11 19:42:28 +00:00
Pleroma Renovate Bot
72cd6b40ea Update dependency vue-eslint-parser to v10.2.0 2025-08-11 09:16:38 +00:00
Pleroma Renovate Bot
e3a7d3dca8 Update dependency msw to v2.10.4 2025-08-11 09:16:20 +00:00
Pleroma Renovate Bot
4ba1e55f05 Update dependency cropperjs to v2.0.1 2025-08-11 09:16:05 +00:00
Ekaterina Vaartis
24c0e0e497 Formatting fixes 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
bf4dbaf077 Fix rich content test 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
a6a2dbd2f7 Fix CSS lint complaint 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
c3872147c0 Correct component name 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
e04d7d2c97 Check if emoji is already a local one 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
0ccff2019f Changelogs 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
04c180e0d9 Allow copying emoji from posts 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
ec635426c3 Allow uploading single emojis from URL 2025-08-11 07:55:48 +00:00
Ekaterina Vaartis
a0159f1e18 Allow copying just one emoji from another pack into a local one 2025-08-11 07:55:48 +00:00
Henry Jameson
41add4fc6a Merge branch 'themes-3-1' into shigusegubu-themes3 2025-08-10 23:39:49 +03:00
Henry Jameson
eef6f6d0e2 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-08-10 23:39:43 +03:00
HJ
d62393bf6b Merge branch 'emoji-pack-upload' into 'develop'
Add a way to upload new packs from URL/ZIP file

See merge request pleroma/pleroma-fe!1999
2025-08-10 20:37:46 +00:00
HJ
1d3b271e7c Apply 1 suggestion(s) to 1 file(s) 2025-08-10 20:30:24 +00:00
Henry Jameson
6e5da62233 changelog 2025-08-10 23:25:42 +03:00
HJ
b80035cbb0 Apply 1 suggestion(s) to 1 file(s) 2025-08-10 20:23:09 +00:00
HJ
047dda5525 Apply 2 suggestion(s) to 1 file(s) 2025-08-10 20:22:39 +00:00
HJ
e82de98892 Merge branch 'renovate/eslint-monorepo' into 'develop'
Update dependency eslint to v9.33.0

See merge request pleroma/pleroma-fe!2172
2025-08-10 20:20:37 +00:00
HJ
cf4aa692e3 Merge branch 'renovate/babel-monorepo' into 'develop'
Update babel monorepo

See merge request pleroma/pleroma-fe!2174
2025-08-10 20:20:29 +00:00
HJ
8f16da2f6f Merge branch 'renovate/nightwatch-3.x' into 'develop'
Update dependency nightwatch to v3.12.2

See merge request pleroma/pleroma-fe!2181
2025-08-10 20:19:51 +00:00
HJ
1f53c8bb07 Merge branch 'renovate/postcss-8.x' into 'develop'
Update dependency postcss to v8.5.6

See merge request pleroma/pleroma-fe!2183
2025-08-10 20:19:33 +00:00
HJ
2830b55d41 Merge branch 'renovate/stylelint-config-recommended-vue-1.x-lockfile' into 'develop'
Update dependency stylelint-config-recommended-vue to v1.6.1

See merge request pleroma/pleroma-fe!2184
2025-08-10 20:19:20 +00:00
HJ
f86cc5d8b5 Merge branch 'renovate/chai-5.x' into 'develop'
Update dependency chai to v5.2.1

See merge request pleroma/pleroma-fe!2208
2025-08-10 20:19:00 +00:00
Henry Jameson
8b8975adb2 even even more weight loss 2025-08-10 23:16:29 +03:00
Henry Jameson
2aabaeb5c6 notification fixes 2025-08-10 23:06:33 +03:00
Henry Jameson
67f606a3b0 user-card fixes 2025-08-10 23:03:50 +03:00
Henry Jameson
9440d35266 even more weight loss 2025-08-10 22:49:09 +03:00
Henry Jameson
6341747ec9 more weight reduction 2025-08-10 22:38:31 +03:00
Henry Jameson
9ec2ff409d panel-header avatar is now hard-coded in user-card 2025-08-10 21:35:59 +03:00
Henry Jameson
4ff257be57 only put heavy components if truly needed 2025-08-10 21:35:19 +03:00
HJ
5e77a0a23d Merge branch 'profile-edit' into 'develop'
Profile edit overhaul

See merge request pleroma/pleroma-fe!2205
2025-08-10 16:41:28 +00:00
Henry Jameson
370a7f8291 lint 2025-08-10 19:37:17 +03:00
Henry Jameson
0f51550802 lint 2025-08-10 17:55:54 +03:00
Henry Jameson
700e096dd4 fix sss 2025-08-10 17:42:37 +03:00
Pleroma Renovate Bot
d879b6f6eb Update dependency eslint to v9.33.0 2025-08-09 09:04:58 +00:00
Pleroma Renovate Bot
8d141cbeab Update dependency chai to v5.2.1 2025-08-08 08:53:03 +00:00
Henry Jameson
651ce2080e Merge branch 'profile-edit' into shigusegubu-themes3 2025-08-08 10:18:02 +03:00
Henry Jameson
81bb4f133b fix drawer styling 2025-08-07 17:28:57 +03:00
Henry Jameson
a4de299c58 improve styles for user_panel 2025-08-07 17:25:04 +03:00
Henry Jameson
11e6349e8d don't use newAvatar/Banner in non-editables 2025-08-07 17:10:56 +03:00
Henry Jameson
409816748e use img instead of background-image since img works with large files but bg-image does not 2025-08-07 17:10:52 +03:00
Henry Jameson
cfb4868c55 change wording because we only upload on "save" 2025-08-07 16:00:11 +03:00
Henry Jameson
f2783260f1 considerably improve styling of upload modal on mobile 2025-08-07 15:44:09 +03:00
Henry Jameson
7fd46f2d6c Revert "fix visibility-notice"
This reverts commit 202bfbad02.
2025-08-07 15:22:03 +03:00
Henry Jameson
f27099a4b3 fix small screens avatar upload 2025-08-07 15:20:33 +03:00
Ekaterina Vaartis
18110d6821 Add a way to upload new packs from URL/ZIP file 2025-08-06 21:52:33 +03:00
Henry Jameson
aa8d09d5a4 Merge branch 'profile-edit' into shigusegubu-themes3 2025-08-05 18:46:00 +03:00
Henry Jameson
202bfbad02 fix visibility-notice 2025-08-05 18:45:42 +03:00
Henry Jameson
2029852784 Merge branch 'profile-edit' into shigusegubu-themes3 2025-08-05 18:38:13 +03:00
Henry Jameson
7b643b5486 lint 2025-08-05 18:33:38 +03:00
Henry Jameson
1c4f19e56f fix 2025-08-05 18:33:09 +03:00
Henry Jameson
d66dd17f7b vuex cleanup 2025-08-05 18:21:49 +03:00
Henry Jameson
76e67a08c6 appearance card fix 2025-08-05 18:20:25 +03:00
Henry Jameson
ebe727b378 user-card cleanup 2025-08-05 18:16:09 +03:00
Henry Jameson
99886ac28c user avatar cleanup 2025-08-05 17:17:01 +03:00
Henry Jameson
d4c0ccf659 profile tab cleanup 2025-08-05 17:09:07 +03:00
Henry Jameson
6e44a3afa9 language switcher cleanup 2025-08-05 17:04:06 +03:00
Henry Jameson
ee01395071 image cropper cleanup 2025-08-05 17:00:21 +03:00
Henry Jameson
3efe6d1243 Merge branch 'profile-edit' into shigusegubu-themes3 2025-08-05 15:24:48 +03:00
Henry Jameson
fa67b2330f fix 2025-08-05 15:20:19 +03:00
Henry Jameson
7e9fd4d1dd lint 2025-08-05 15:17:07 +03:00
Henry Jameson
bc2964c327 dirty state 2025-08-05 14:58:53 +03:00
Henry Jameson
3311c676ad better layout & wording 2025-08-05 14:43:37 +03:00
Henry Jameson
184f1cdc24 remove useless stuff 2025-08-05 14:39:15 +03:00
Henry Jameson
8268d0d349 better headers 2025-08-05 14:18:09 +03:00
Henry Jameson
a4e6a72ca2 fix banner overflowing 2025-08-05 14:10:07 +03:00
Henry Jameson
49422705a8 fixed interaction button size 2025-08-05 13:59:56 +03:00
Henry Jameson
23d53e9fd0 Merge branch 'profile-edit' into shigusegubu-themes3 2025-08-05 00:27:58 +03:00
Henry Jameson
d34ce95d17 scale bottom line too 2025-08-05 00:27:20 +03:00
Henry Jameson
fc90a92ecf better interaction buttons. can't get them quite right - limitation of html? 2025-08-05 00:24:00 +03:00
Henry Jameson
ea3e054c21 changelog 2025-08-04 23:16:27 +03:00
Henry Jameson
d89f564b5e proper resets, better upload dialog 2025-08-04 23:15:26 +03:00
Henry Jameson
a4802030be visible role preview 2025-08-04 14:25:14 +03:00
Henry Jameson
86f8f46b95 i have no idea how this worked before, but now it does fr 2025-08-04 14:22:20 +03:00
Henry Jameson
41267a5d43 small fixes 2025-08-04 14:09:25 +03:00
Henry Jameson
c8fa72c791 move background to appearance tab 2025-08-04 14:04:28 +03:00
Henry Jameson
800ab90cf9 reorganization of some settings 2025-08-04 13:48:09 +03:00
Henry Jameson
59de80639f small updates 2025-08-04 11:34:41 +03:00
Henry Jameson
f79c61c4e7 name/bio/avatar/banner edit support 2025-08-04 11:10:43 +03:00
Henry Jameson
b305748a92 avatar upload works 2025-08-04 03:35:09 +03:00
Henry Jameson
7d985bd475 misc 2025-08-04 00:45:13 +03:00
Henry Jameson
60363e66fb editable name 2025-08-04 00:14:34 +03:00
Henry Jameson
50314fe253 better fields 2025-08-03 23:15:35 +03:00
Henry Jameson
20beb30fc3 bio editable 2025-08-03 23:05:16 +03:00
Henry Jameson
2df895ab02 editable meta and bdey 2025-08-03 21:56:45 +03:00
Henry Jameson
51eb61180d fix missing computed in general tab 2025-08-03 18:32:18 +03:00
HJ
38b9b04385 Merge branch 'profile-overhaul' into 'develop'
Profile overhaul

Closes #1375

See merge request pleroma/pleroma-fe!2202
2025-08-03 15:06:58 +00:00
Henry Jameson
28422adc8c lint 2025-08-03 18:01:09 +03:00
Henry Jameson
3c2c572661 lint 2025-08-03 17:51:27 +03:00
HJ
39602f36bb Merge branch 'block-expiration-param' into 'develop'
Correct the block expiration parameter name

See merge request pleroma/pleroma-fe!2203
2025-08-02 22:53:42 +00:00
Ekaterina Vaartis
68c88677a0 Correct the block expiration parameter name 2025-08-02 00:35:19 +03:00
Henry Jameson
5c86951798 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-08-01 02:20:01 +03:00
Henry Jameson
d82ab81e4e make popovers even more nicer 2025-08-01 02:19:48 +03:00
Henry Jameson
5b030bc863 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-08-01 02:13:56 +03:00
Henry Jameson
66b83558bb fix self popover... again 2025-08-01 02:13:44 +03:00
Henry Jameson
a06d8a3186 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-08-01 00:59:11 +03:00
Henry Jameson
9a85c3f8fb save some space in popovers 2025-08-01 00:58:59 +03:00
Henry Jameson
0eac23daba Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-31 22:46:44 +03:00
Henry Jameson
34c29e4fdc enforce min height to prevent buttonless (self) popovers from being tiny 2025-07-31 22:46:06 +03:00
Henry Jameson
63e6527d2d Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-31 22:35:16 +03:00
Henry Jameson
6e7ad5d554 fuck it, i dunno why it doesn't work right without flex 2025-07-31 22:34:46 +03:00
Henry Jameson
741523e51a Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-31 22:02:10 +03:00
Henry Jameson
07a6d58660 fix lock icon 2025-07-31 22:02:04 +03:00
Henry Jameson
22dc4ff983 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-31 21:44:22 +03:00
Henry Jameson
ddcd9007dc improve styles again 2025-07-31 21:44:08 +03:00
Henry Jameson
59266b30ed Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-31 21:36:08 +03:00
Henry Jameson
f8a5918fc8 improve other-actions again 2025-07-31 21:35:43 +03:00
Henry Jameson
bb044315cd improve gradient styles again 2025-07-31 21:29:58 +03:00
Henry Jameson
426fb90522 fix short names 2025-07-31 21:28:34 +03:00
Henry Jameson
2008fcce22 simpligy gradient style and improve other-actions layout 2025-07-31 21:28:00 +03:00
Henry Jameson
a616b20c7c Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-30 23:20:45 +03:00
Henry Jameson
ba5efe4a67 fix tags showing up when they shouldn't 2025-07-30 16:53:33 +03:00
Henry Jameson
83ad4078bb fix stats on small screens 2025-07-30 16:53:22 +03:00
Henry Jameson
57059b3297 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-30 10:29:39 +03:00
Henry Jameson
41d794d3ab fix border on popovers 2025-07-30 10:29:28 +03:00
Henry Jameson
8a1dbcdcff Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-30 10:22:50 +03:00
Henry Jameson
eb68857293 user tags overflow 2025-07-30 10:22:37 +03:00
Henry Jameson
e3e0d1f3f4 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-30 10:05:03 +03:00
Henry Jameson
add9535b1a fix overflows and small screens 2025-07-30 10:00:03 +03:00
Henry Jameson
2c68d1512d Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-30 01:20:40 +03:00
Henry Jameson
1fdb676eca better buttons sizing 2025-07-30 01:20:27 +03:00
Henry Jameson
be56291869 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-30 01:13:34 +03:00
Henry Jameson
9e77071e77 requested options 2025-07-30 01:13:24 +03:00
Henry Jameson
f5d167950d proper bottom spacing in user popover 2025-07-30 01:03:47 +03:00
Henry Jameson
82db998c19 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-30 00:40:39 +03:00
Henry Jameson
4ffd8499a7 scale avatar and user title with container width 2025-07-30 00:30:07 +03:00
Henry Jameson
b05ebeaed5 made profile action buttons easier to press 2025-07-30 00:29:46 +03:00
Henry Jameson
518144f308 changelog 2025-07-29 23:46:39 +03:00
Henry Jameson
a1d3d04087 Merge branch 'profile-overhaul' into shigusegubu-themes3 2025-07-29 19:03:39 +03:00
Henry Jameson
731e9eed6e style updates 2025-07-29 19:00:45 +03:00
Henry Jameson
0b1e7dbcea simpler format for bdey/stats 2025-07-29 18:38:41 +03:00
Henry Jameson
ef0a4f4023 "fix" select styles 2025-07-29 17:52:52 +03:00
HJ
2c1bea0e0b Merge branch 'whats-this-doing-here' into 'develop'
what's this doing here, begone

See merge request pleroma/pleroma-fe!2201
2025-07-29 14:42:16 +00:00
Henry Jameson
d299e864bf what's this doing here, begone 2025-07-29 17:41:18 +03:00
Henry Jameson
79868aeeb9 initial profile overhaul 2025-07-29 17:38:08 +03:00
HJ
38130fce90 Merge branch 'misc/lint-and-markup' into 'develop'
Lint some things

See merge request pleroma/pleroma-fe!2199
2025-07-27 17:28:21 +00:00
Pleroma User
3294df0325 Lint some things 2025-07-27 17:28:21 +00:00
Pleroma Renovate Bot
0e56f8f103 Update babel monorepo 2025-07-25 08:52:58 +00:00
HJ
e747ee896e Merge branch 'timed-user-mutes' into 'develop'
Timed user mutes

See merge request pleroma/pleroma-fe!2197
2025-07-18 14:17:43 +00:00
Henry Jameson
33a0e4b2b9 Merge branch 'firefix' into shigusegubu-themes3 2025-07-17 17:57:38 +03:00
HJ
e1cbb1ccd2 Merge branch 'firefix' into 'develop'
Fix firefox and theme change not working

See merge request pleroma/pleroma-fe!2198
2025-07-17 14:56:17 +00:00
Henry Jameson
04ee1a892c proper detection 2025-07-17 17:55:20 +03:00
Henry Jameson
23975b506e fix theme change not working 2025-07-17 17:50:38 +03:00
Henry Jameson
c12e1a69cf fix firefox 2025-07-17 17:32:09 +03:00
Henry Jameson
6aae8a8705 move "ask" options to filtering tab 2025-07-17 16:20:16 +03:00
Henry Jameson
6ae52e192b cleanup 2025-07-17 16:13:35 +03:00
Henry Jameson
eb77c5dbb9 changelog 2025-07-17 15:23:05 +03:00
Henry Jameson
b014489295 better modal layout 2025-07-17 15:22:03 +03:00
Henry Jameson
35c2e87131 show expiry only if available 2025-07-17 15:22:03 +03:00
Henry Jameson
081a77ee0c Merge branch 'timed-user-mutes' into shigusegubu-themes3 2025-07-09 19:05:47 +03:00
Henry Jameson
9f0b65654e show expirety in user profile (currently broken?) 2025-07-09 19:03:58 +03:00
Henry Jameson
2441e6508d settings for confirmations 2025-07-09 18:37:05 +03:00
Henry Jameson
71d1baffcc better support for lack of block expiration 2025-07-09 17:56:52 +03:00
Henry Jameson
8436f39eff block/mute cards update to show expiry and ask for it 2025-07-09 17:45:13 +03:00
Henry Jameson
385f921c41 Merge remote-tracking branch 'origin/develop' into timed-user-mutes 2025-07-09 15:57:23 +03:00
HJ
74b410da2b Merge branch 'fix-zoom-lag' into 'develop'
fix zoom being applied with a delay

See merge request pleroma/pleroma-fe!2196
2025-07-09 12:53:51 +00:00
Henry Jameson
d9639c543c chore 2025-07-09 15:49:54 +03:00
Henry Jameson
c433aa38fb fix 2025-07-09 15:48:36 +03:00
Henry Jameson
a626b37354 fix zoom being applied with a delay 2025-07-09 15:46:05 +03:00
HJ
2e9f23542c Merge branch 'redmond-fix' into 'develop'
Redmond fix

See merge request pleroma/pleroma-fe!2195
2025-07-03 18:22:16 +00:00
Henry Jameson
1d729cb0c5 fix and default 2025-07-03 21:21:21 +03:00
Henry Jameson
0f573637a7 fix redmond 2025-07-03 21:19:12 +03:00
Henry Jameson
b2f7309e1e sorting, typo 2025-07-03 17:11:23 +03:00
HJ
c179daaf80 Merge branch 'fix-style-editors' into 'develop'
Fix style editors, properly default from unavailable adopted sheets

See merge request pleroma/pleroma-fe!2194
2025-07-03 08:25:18 +00:00
Henry Jameson
637bbf5803 Merge branch 'fix-style-editors' into shigusegubu-themes3 2025-07-03 11:21:04 +03:00
Henry Jameson
558251ce74 back to kazv upstream of pinchzoom 2025-07-03 11:20:45 +03:00
Henry Jameson
b6ed146b5a Merge branch 'fix-style-editors' into shigusegubu-themes3 2025-07-03 10:43:21 +03:00
Henry Jameson
7bc5dd440b remove backdrop-filter only if unsupported 2025-07-03 01:55:43 +03:00
Henry Jameson
a635f025be chore 2025-07-03 00:20:48 +03:00
Henry Jameson
17348b9823 Merge branch 'fix-style-editors' into shigusegubu-themes3 2025-07-02 23:26:59 +03:00
Henry Jameson
1bc53262d6 theme tab support 2025-07-02 23:24:19 +03:00
Henry Jameson
d6ebc5049e cleanup 2025-07-02 23:24:12 +03:00
Henry Jameson
a55d571a44 Merge branch 'fix-style-editors' into shigusegubu-themes3 2025-07-02 22:55:29 +03:00
Henry Jameson
3081504c64 update tabs to use new API 2025-07-02 22:54:45 +03:00
Henry Jameson
dc531d4ef3 streamline custom styles additions 2025-07-02 19:39:25 +03:00
Henry Jameson
5a6a77bd75 update component preview on render 2025-07-02 18:20:05 +03:00
Henry Jameson
bc96d16e11 small fix 2025-07-02 18:20:05 +03:00
Henry Jameson
82d67a634e undo link->style 2025-07-02 18:20:05 +03:00
Henry Jameson
3822aaf137 i spent too much time on this 2025-07-02 18:20:05 +03:00
Henry Jameson
0d32a7ddac fix component preview styles 2025-07-02 18:20:05 +03:00
Henry Jameson
8cfae10f37 update component preview on render 2025-07-02 18:15:12 +03:00
Henry Jameson
5a65e1d895 small fix 2025-07-02 18:14:56 +03:00
Henry Jameson
1e26b9188c undo link->style 2025-07-02 10:42:44 +03:00
Henry Jameson
567bccb9fa i spent too much time on this 2025-07-02 02:26:17 +03:00
Henry Jameson
1cb4740fc9 fix component preview styles 2025-07-02 00:36:37 +03:00
Henry Jameson
980917a16f Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-06-29 16:40:54 +03:00
Henry Jameson
9cac70d8e6 Merge branch 'akkoma-support-part-2' into shigusegubu-themes3 2025-06-28 22:48:12 +03:00
Henry Jameson
2f70309970 Merge branch 'akkoma-support-part-2' into shigusegubu-themes3 2025-06-28 22:31:30 +03:00
Henry Jameson
a72dc45f46 Merge branch 'csp-akkoma' into shigusegubu-themes3 2025-06-28 16:05:44 +03:00
Henry Jameson
dcdb5a8ab5 Merge branch 'csp-akkoma' into shigusegubu-themes3 2025-06-28 15:57:47 +03:00
Pleroma Renovate Bot
8483268cb3 Update dependency stylelint-config-recommended-vue to v1.6.1 2025-06-27 09:04:51 +00:00
Pleroma Renovate Bot
230e61235d Update dependency postcss to v8.5.6 2025-06-27 09:04:41 +00:00
Henry Jameson
e4b44f8c7b better dialog, actually working dontAskAgain 2025-06-26 16:28:02 +03:00
Pleroma Renovate Bot
046b959bd7 Update dependency pinia to v3.0.3 2025-06-26 09:04:52 +00:00
Pleroma Renovate Bot
4146c071ce Update dependency nightwatch to v3.12.2 2025-06-26 09:04:43 +00:00
Henry Jameson
a3d857f0f9 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-06-26 00:09:42 +03:00
Henry Jameson
3a59fddb4a Merge branch 'smallfixes' into shigusegubu-themes3 2025-06-26 00:09:29 +03:00
Henry Jameson
182c707436 Merge branch 'from/develop/tusooa/sw-cache-assets' into shigusegubu-themes3 2025-06-25 15:54:40 +03:00
Henry Jameson
109882a68e Merge branch 'akkoma' into shigusegubu-themes3 2025-06-24 17:05:57 +03:00
Pleroma Renovate Bot
537031f0cb Update dependency @ruffle-rs/ruffle to v0.1.0-nightly.2025.6.22 2025-06-22 09:05:47 +00:00
Henry Jameson
a2391a11cf Merge branch 'akkoma' into shigusegubu-themes3 2025-06-18 19:58:45 +03:00
Henry Jameson
9b555a790d Merge branch 'akkoma' into shigusegubu-themes3 2025-06-18 19:55:51 +03:00
Henry Jameson
3695774f8e clarity 2025-06-18 19:55:43 +03:00
Henry Jameson
25620f1cea Merge branch 'akkoma' into shigusegubu-themes3 2025-06-18 19:54:14 +03:00
Henry Jameson
53213e40de Merge branch 'akkoma' into shigusegubu-themes3 2025-06-18 19:48:07 +03:00
Henry Jameson
c9a4aee954 forgotten files 2025-06-12 22:03:15 +03:00
Henry Jameson
b9161ef697 some basic expiration modal. "don't as again" doesn't work yet 2025-06-12 20:04:39 +03:00
Henry Jameson
4a1914d71c Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-05-21 20:47:48 +03:00
Henry Jameson
afbf63412d Merge branch 'sss-objects' into shigusegubu-themes3 2025-04-11 11:52:01 +03:00
Henry Jameson
aff02e393d Merge branch 'sss-objects' into shigusegubu-themes3 2025-04-09 00:36:18 +03:00
Henry Jameson
e6fc460b02 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-04-08 15:07:19 +03:00
Henry Jameson
89d45a86e5 Merge branch 'fix-word-break' into shigusegubu-themes3 2025-04-03 19:04:56 +03:00
Henry Jameson
2fcbebc995 Merge branch 'fix-word-break' into shigusegubu-themes3 2025-04-03 12:45:33 +03:00
Henry Jameson
f068549adb Merge branch 'fix-word-break' into shigusegubu-themes3 2025-04-03 11:30:56 +03:00
Henry Jameson
b43c556a84 Merge branch 'fix-word-break' into shigusegubu-themes3 2025-04-03 10:56:32 +03:00
Henry Jameson
aeef5c77de Merge branch 'sss-objects' into shigusegubu-themes3 2025-04-03 01:44:12 +03:00
Henry Jameson
efe8dbf921 Merge branch 'fix-bookrmarkdropdown' into shigusegubu-themes3 2025-03-27 16:21:45 +02:00
Henry Jameson
10329e9f45 Merge branch 'fix-breezy' into shigusegubu-themes3 2025-03-26 22:17:23 +02:00
Henry Jameson
c968347c0a undo config.json change because there's no proper way to set those in either UI 2025-03-26 16:08:47 +02:00
Henry Jameson
425d0662a1 remove sgsgb-related stuff 2025-03-26 16:01:58 +02:00
Henry Jameson
55d29065f0 Merge branch 'sss-objects' into shigusegubu-themes3 2025-03-26 15:58:15 +02:00
Henry Jameson
515d5451fa make a separate file to store all information about fields and their migrations 2025-03-26 15:57:48 +02:00
Henry Jameson
47a6777284 Merge branch 'sss-objects' into shigusegubu-themes3 2025-03-25 21:26:30 +02:00
Henry Jameson
42f51ff315 Merge branch 'develop' into shigusegubu-themes3 2025-03-25 20:09:01 +02:00
Henry Jameson
add0e5f934 Merge branch 'sss-objects' into shigusegubu-themes3 2025-03-25 19:58:31 +02:00
Henry Jameson
350dd2fcf8 Merge branch 'fixes-roundup5' into shigusegubu-themes3 2025-03-13 15:24:57 +02:00
Henry Jameson
155d9b0999 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-03-13 13:00:21 +02:00
Henry Jameson
f051de13e7 Merge branch 'fixes-roundup5' into shigusegubu-themes3 2025-03-13 13:00:12 +02:00
Henry Jameson
b058bab73a Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-03-13 02:14:34 +02:00
Henry Jameson
06e42053b2 Merge branch 'fixes-roundup5' into shigusegubu-themes3 2025-03-13 02:14:12 +02:00
Henry Jameson
8ad1c4a3ff Merge branch 'fixes-roundup5' into shigusegubu-themes3 2025-03-05 02:51:15 +02:00
Henry Jameson
af432072ca Merge branch 'tusooa/vite' into shigusegubu-themes3 2025-03-04 19:04:15 +02:00
Henry Jameson
52b1c3d21a Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-02-20 15:15:59 +02:00
Henry Jameson
d95eb5be85 Merge branch 'fixes-roundup4' into shigusegubu-themes3 2025-02-20 15:15:47 +02:00
Henry Jameson
9d92fb45a2 Merge branch 'fixes-roundup4' into shigusegubu-themes3 2025-02-19 16:22:46 +02:00
Henry Jameson
83e3a6c628 Merge branch 'fixes-roundup4' into shigusegubu-themes3 2025-02-18 22:40:41 +02:00
Henry Jameson
4dbb275c48 Merge branch 'fixes-roundup4' into shigusegubu-themes3 2025-02-12 22:06:31 +02:00
Henry Jameson
01bf0e8261 Merge branch 'fixes-roundup4' into shigusegubu-themes3 2025-02-12 15:54:59 +02:00
Henry Jameson
fe5edb7db9 Merge branch 'fixes-roundup4' into shigusegubu-themes3 2025-02-12 15:46:16 +02:00
Henry Jameson
33a239b89b Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-02-10 23:22:19 +02:00
Henry Jameson
5a918d94df Merge branch 'fixes-roundup4' into shigusegubu-themes3 2025-02-10 23:21:25 +02:00
Henry Jameson
fb609674ad huh? 2025-02-05 08:47:10 +02:00
Henry Jameson
0ed64938b3 Merge branch 'fixes-roundup4' into shigusegubu-themes3 2025-02-05 08:43:38 +02:00
Henry Jameson
c11ebb1e7f Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-02-03 21:45:58 +02:00
Henry Jameson
2d26737bab Merge branch 'migrate/vuex-to-pinia' into shigusegubu-themes3 2025-02-03 00:15:01 +02:00
Henry Jameson
f4934ec723 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-01-28 17:30:36 +02:00
Henry Jameson
c1ac4674af Merge branch 'fixes-roundup3' into shigusegubu-themes3 2025-01-27 17:56:23 +02:00
Henry Jameson
3711dfa913 Merge branch 'fixes-roundup3' into shigusegubu-themes3 2025-01-27 13:30:35 +02:00
Henry Jameson
26abe6b403 Merge branch 'fixes-roundup3' into shigusegubu-themes3 2025-01-26 22:51:49 +02:00
Henry Jameson
90bc03d394 Merge branch 'customizable-post-actions' into shigusegubu-themes3 2025-01-21 10:53:19 +02:00
Henry Jameson
6baf0ec339 Merge branch 'customizable-post-actions' into shigusegubu-themes3 2025-01-20 01:58:29 +02:00
Henry Jameson
f246611aa2 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-01-19 18:27:09 +02:00
Henry Jameson
19277f6cd5 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2025-01-19 18:26:50 +02:00
Henry Jameson
2b4052a2c8 Merge branch 'customizable-post-actions' into shigusegubu-themes3 2025-01-19 18:26:46 +02:00
Henry Jameson
04c5699c5a Merge branch 'customizable-post-actions' into shigusegubu-themes3 2025-01-19 17:23:55 +02:00
Henry Jameson
e92ecc612f Merge branch 'customizable-post-actions' into shigusegubu-themes3 2025-01-18 21:45:25 +02:00
Henry Jameson
478779121d Merge branch 'customizable-post-actions' into shigusegubu-themes3 2025-01-16 20:14:51 +02:00
Henry Jameson
f6841decce Merge branch 'fixes-batch2' into shigusegubu-themes3 2025-01-08 22:09:31 +02:00
Henry Jameson
1f37c53ab3 Merge branch 'consistent-mutes' into shigusegubu-themes3 2025-01-04 02:04:30 +02:00
Henry Jameson
3ffe042b25 Merge branch 'consistent-mutes' into shigusegubu-themes3 2025-01-02 23:03:37 +02:00
Henry Jameson
e498c51006 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2024-12-31 13:26:31 +02:00
Henry Jameson
72c980dbc3 Merge branch 'drafts-improvements' into shigusegubu-themes3 2024-12-31 12:12:17 +02:00
Henry Jameson
a59c9557a8 Merge branch 'drafts-improvements' into shigusegubu-themes3 2024-12-31 01:07:54 +02:00
Henry Jameson
9c952173f4 Merge branch 'themes-updates' into shigusegubu-themes3 2024-12-30 21:03:55 +02:00
Henry Jameson
cd9ed41d2d Merge branch 'themes-updates' into shigusegubu-themes3 2024-12-30 16:40:42 +02:00
Henry Jameson
b7c3cc30de Merge branch 'themes-updates' into shigusegubu-themes3 2024-12-30 16:32:56 +02:00
Henry Jameson
d679642a19 Merge branch 'drafts-improvements' into shigusegubu-themes3 2024-12-30 03:10:42 +02:00
Henry Jameson
1348bbb4d2 Merge branch 'mobile-modals' into shigusegubu-themes3 2024-12-29 22:30:04 +02:00
Henry Jameson
e8338a2f65 Merge branch 'mobile-modals' into shigusegubu-themes3 2024-12-29 18:56:14 +02:00
Henry Jameson
5e65c2efb7 Merge branch 'themes-updates' into shigusegubu-themes3 2024-12-29 18:16:54 +02:00
Henry Jameson
d1db92c896 Merge branch 'drafts-improvements' into shigusegubu-themes3 2024-12-27 17:10:34 +02:00
Henry Jameson
727253aba3 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2024-12-27 02:11:21 +02:00
Henry Jameson
38ea52d38b Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2024-12-27 02:11:03 +02:00
Henry Jameson
8809424d75 Merge branch 'emoji-fixes' into shigusegubu-themes3 2024-12-27 00:29:29 +02:00
Henry Jameson
4b89c96799 Merge branch 'themes-updates' into shigusegubu-themes3 2024-12-27 00:29:21 +02:00
Henry Jameson
1a306c2723 Merge branch 'themes-updates' into shigusegubu-themes3 2024-12-24 16:24:51 +02:00
Henry Jameson
2d459df039 Merge branch 'emoji-fixes' into shigusegubu-themes3 2024-12-24 11:57:01 +02:00
Henry Jameson
c2e750daf7 Merge branch 'emoji-fixes' into shigusegubu-themes3 2024-12-24 11:12:00 +02:00
Henry Jameson
5dac99d828 Merge branch 'emoji-fixes' into shigusegubu-themes3 2024-12-23 23:16:05 +02:00
Henry Jameson
49dba9cfab Merge branch 'fixes-batch' into shigusegubu-themes3 2024-12-23 14:16:02 +02:00
Henry Jameson
48ffb39bf8 Merge branch 'fixes-batch' into shigusegubu-themes3 2024-12-23 04:45:48 +02:00
Henry Jameson
53ed51f9e5 Merge branch 'fixes-batch' into shigusegubu-themes3 2024-12-23 04:42:32 +02:00
Henry Jameson
3cb0dec5e1 Merge branch 'fixes-batch' into shigusegubu-themes3 2024-12-19 15:33:22 +02:00
Henry Jameson
5f3fcad181 Merge branch 'fixes-batch' into shigusegubu-themes3 2024-12-19 14:56:00 +02:00
Henry Jameson
66febc0756 Merge branch 'loader-fixes' into shigusegubu-themes3 2024-12-12 19:03:24 +02:00
Henry Jameson
108b8fe3f8 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-12-10 15:56:05 +02:00
Henry Jameson
11072e5fc8 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-12-04 15:55:09 +02:00
Henry Jameson
4503bd77d7 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-12-04 14:04:04 +02:00
Henry Jameson
3af29e78b9 when switching to new style, use "native" palette, also show it in appearance tab 2024-12-04 14:03:27 +02:00
Henry Jameson
a0e12cabc6 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-12-01 22:26:52 +02:00
Henry Jameson
749d08054c use breezy theme by default 2024-11-28 18:17:33 +02:00
Henry Jameson
2f10b94af8 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-11-28 18:10:16 +02:00
Henry Jameson
eb5f47ebf9 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-11-26 02:02:02 +02:00
Henry Jameson
31527054c2 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-11-19 03:20:04 +02:00
Henry Jameson
42087a564f bundling theme now works and so are bundled style's palettes 2024-11-19 03:18:52 +02:00
Henry Jameson
89b0ed83f2 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-11-15 00:40:24 +02:00
Henry Jameson
ca84e08247 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-11-12 23:24:54 +02:00
Henry Jameson
1b644370b1 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-30 22:52:14 +02:00
Henry Jameson
ba0855ad64 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-29 23:55:57 +02:00
Henry Jameson
ca57af0a43 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-29 21:17:48 +02:00
Henry Jameson
a023c44bee Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-28 21:23:14 +02:00
Henry Jameson
0562fe1c44 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-25 16:58:48 +03:00
Henry Jameson
461eb8752d Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-22 10:08:40 +03:00
Henry Jameson
f03df3c993 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-13 17:13:26 +03:00
Henry Jameson
eac63e0c57 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-13 17:09:57 +03:00
Henry Jameson
0da9ba1b7a Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-07 12:30:07 +03:00
Henry Jameson
f8b39faae7 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-07 12:25:29 +03:00
Henry Jameson
104bc8c86e oops 2024-10-06 01:57:39 +03:00
Henry Jameson
d647cd82d1 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-06 01:49:46 +03:00
Henry Jameson
e2705f57b1 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-05 23:15:15 +03:00
Henry Jameson
761cc52be7 palette and style for v3 2024-10-05 19:31:57 +03:00
Henry Jameson
f161cc4113 restore sgsgb palettes 2024-10-05 19:31:04 +03:00
Henry Jameson
6946626217 fix? 2024-10-05 19:24:32 +03:00
Henry Jameson
b0b6380ea4 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-05 19:07:09 +03:00
Henry Jameson
d818b47938 Merge branch 'themes3-grand-finale-maybe' into shigusegubu-themes3 2024-10-05 18:54:18 +03:00
Henry Jameson
16610c8a12 Merge remote-tracking branch 'origin/develop' into shigusegubu-themes3 2024-10-05 18:53:54 +03:00
Henry Jameson
8163366402 Merge remote-tracking branch 'origin/develop' into shigusegubu-clean 2024-10-03 02:19:07 +03:00
Henry Jameson
2884d6e9ba Merge branch 'admin-dashboard-fixes' into shigusegubu-clean 2023-11-22 21:58:32 +02:00
584 changed files with 67393 additions and 23407 deletions

12
.dockerignore Normal file
View file

@ -0,0 +1,12 @@
node_modules/
dist/
logs/
.DS_Store
.git/
config/local.json
pleroma-backend/
test/e2e/reports/
test/e2e-playwright/test-results/
test/e2e-playwright/playwright-report/
__screenshots__/

3
.gitignore vendored
View file

@ -4,8 +4,11 @@ dist/
npm-debug.log
test/unit/coverage
test/e2e/reports
test/e2e-playwright/test-results
test/e2e-playwright/playwright-report
selenium-debug.log
.idea/
.gitlab-ci-local/
config/local.json
src/assets/emoji.json
logs/

View file

@ -34,12 +34,23 @@ check-changelog:
- apk add git
- sh ./tools/check-changelog
lint:
lint-eslint:
stage: lint
script:
- yarn
- yarn lint
- yarn stylelint
- yarn ci-eslint
lint-biome:
stage: lint
script:
- yarn
- yarn ci-biome
lint-stylelint:
stage: lint
script:
- yarn
- yarn ci-stylelint
test:
stage: test
@ -60,6 +71,135 @@ test:
- test/**/__screenshots__
when: on_failure
e2e-pleroma:
stage: test
image: mcr.microsoft.com/playwright:v1.57.0-jammy
services:
- name: postgres:15-alpine
alias: db
- name: $PLEROMA_IMAGE
alias: pleroma
entrypoint: ["/bin/ash", "-c"]
command:
- |
set -eu
SEED_SENTINEL_PATH=/var/lib/pleroma/.e2e_seeded
CONFIG_OVERRIDE_PATH=/var/lib/pleroma/config.exs
echo '-- Waiting for database...'
while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma} -t 1; do
sleep 1s
done
echo '-- Writing E2E config overrides...'
cat > $CONFIG_OVERRIDE_PATH <<EOF
import Config
config :pleroma, Pleroma.Captcha,
enabled: false
config :pleroma, :instance,
registrations_open: true,
account_activation_required: false,
approval_required: false
EOF
echo '-- Running migrations...'
/opt/pleroma/bin/pleroma_ctl migrate
echo '-- Starting!'
/opt/pleroma/bin/pleroma start &
PLEROMA_PID=$!
cleanup() {
if kill -0 $PLEROMA_PID 2>/dev/null; then
kill -TERM $PLEROMA_PID
wait $PLEROMA_PID || true
fi
}
trap cleanup INT TERM
echo '-- Waiting for API...'
api_ok=false
for _i in $(seq 1 120); do
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
api_ok=true
break
fi
sleep 1s
done
if [ $api_ok != true ]; then
echo 'Timed out waiting for Pleroma API to become available'
exit 1
fi
if [ ! -f $SEED_SENTINEL_PATH ]; then
if [ -n ${E2E_ADMIN_USERNAME:-} ] && [ -n ${E2E_ADMIN_PASSWORD:-} ] && [ -n ${E2E_ADMIN_EMAIL:-} ]; then
echo '-- Seeding admin user' $E2E_ADMIN_USERNAME '...'
if ! /opt/pleroma/bin/pleroma_ctl user new $E2E_ADMIN_USERNAME $E2E_ADMIN_EMAIL --admin --password $E2E_ADMIN_PASSWORD -y; then
echo '-- User already exists or creation failed, ensuring admin + confirmed...'
/opt/pleroma/bin/pleroma_ctl user set $E2E_ADMIN_USERNAME --admin --confirmed
fi
else
echo '-- Skipping admin seeding (missing E2E_ADMIN_* env)'
fi
touch $SEED_SENTINEL_PATH
fi
wait $PLEROMA_PID
tags:
- amd64
- himem
variables:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
FF_NETWORK_PER_BUILD: "true"
PLEROMA_IMAGE: git.pleroma.social:5050/pleroma/pleroma:stable
POSTGRES_USER: pleroma
POSTGRES_PASSWORD: pleroma
POSTGRES_DB: pleroma
DB_USER: pleroma
DB_PASS: pleroma
DB_NAME: pleroma
DB_HOST: db
DB_PORT: 5432
DOMAIN: localhost
INSTANCE_NAME: Pleroma E2E
E2E_ADMIN_USERNAME: admin
E2E_ADMIN_PASSWORD: adminadmin
E2E_ADMIN_EMAIL: admin@example.com
ADMIN_EMAIL: $E2E_ADMIN_EMAIL
NOTIFY_EMAIL: $E2E_ADMIN_EMAIL
VITE_PROXY_TARGET: http://pleroma:4000
VITE_PROXY_ORIGIN: http://localhost:4000
E2E_BASE_URL: http://localhost:8080
script:
- npm install -g yarn@1.22.22
- yarn --frozen-lockfile
- |
echo "-- Waiting for Pleroma API..."
api_ok="false"
for _i in $(seq 1 120); do
if wget -qO- http://pleroma:4000/api/v1/instance >/dev/null 2>&1; then
api_ok="true"
break
fi
sleep 1s
done
if [ "$api_ok" != "true" ]; then
echo "Timed out waiting for Pleroma API to become available"
exit 1
fi
- yarn e2e:pw
artifacts:
when: on_failure
paths:
- test/e2e-playwright/test-results
- test/e2e-playwright/playwright-report
build:
stage: build
tags:

View file

@ -0,0 +1,8 @@
### Release checklist
* [ ] Bump version in `package.json`
* [ ] Compile a changelog with the `tools/collect-changelog` script
* [ ] Create an MR with an announcement to pleroma.social
#### post-merge
* [ ] Tag the release on the merge commit
* [ ] Make the tag into a Gitlab Release™
* [ ] Merge `master` into `develop` (in case the fixes are already in develop, use `git merge -s ours --no-commit` and manually merge the changelogs)

View file

@ -12,6 +12,8 @@
"custom-property-pattern": null,
"keyframes-name-pattern": null,
"scss/operator-no-newline-after": null,
"declaration-property-value-no-unknown": true,
"scss/declaration-property-value-no-unknown": true,
"declaration-block-no-redundant-longhand-properties": [
true,
{

View file

@ -2,6 +2,67 @@
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/).
## 2.10.0
### Changed
- Temporary changes modal now shows actual countdown instead of fixed timeout
- Disabled elements are more disabled now
- Rearranged and split settings to make more sense and be less of a wall of text
- On mobile settings now take up full width and presented in navigation style
improved styles for settings
### Added
- Most of the remaining AdminFE tabs were added into Admin Dashboard
- It's now possible to customize PWA Manfiest from PleromaFE
- Make every configuration option default-overridable by instance admins
### Fixed
- Fixed settings not appearing if user never touched "show advanced" toggle
- Fix display of the broken/deleted/banned users
- Fixed incorrect emoji display in post interaction lists
- Fixed list title not being saved when editing
- Fixed poll notifications not being expandable
## 2.9.3
### Fixed
- Being unable to update profile
## 2.9.2
### Changed
- BREAKING: due to some internal technical changes logging into AdminFE through PleromaFE is no longer possible
- User card/profile got an overhaul
- Profile editing overhaul
- Visually combined subject and content fields in post form
- Moved post form's emoji button into input field
- Minor visual changes and fixes
- Clicking on fav/rt/emoji notifications' contents expands/collapses it
- Reduced time taken processing theme by half
- Splash screen only appears if loading takes more than 2 seconds
### Added
- Mutes received an update, adding support for regex, muting based on username and expiration time.
- Mutes are now synchronized across sessions
- Support for expiring mutes and blocks (if available)
- Clicking on emoji shows bigger version of it alongside with its shortcode
- Admins also are able to copy it into a local pack
- Added support for Akkoma and IceShrimp.NET backends
- Compatibility with stricter CSP (Akkoma backend)
- Added a way to upload new packs from a URL or ZIP file via the Admin Dashboard
- Unify show/hide content buttons
- Add support for detachable scrollTop button
- Option to left-align user bio
- Cache assets and emojis with service worker
- Indicate currently active V3 theme as a body element class
- Add arithmetic blend ISS function
### Fixed
- Display counter for status action buttons when they are in the menu
- Fix bookmark button alignment in the extra actions menu
- Instance favicons are no longer stretched
- A lot more scalable UI fixes
- Emoji picker now should work fine when emoji size is increased
## 2.8.0
### Changed
- BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image
@ -34,8 +95,8 @@ This does not guarantee that browsers will or will not work.
- Support displaying time in absolute format
- Add draft management system
- Compress most kinds of images on upload.
- Added option to always convert images to JPEG format instead of using WebP when compressing images.
- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
- Added option to always convert images to JPEG format instead of using WebP when compressing images.
- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
- Inform users that Smithereen public polls are public
- Splash screen + loading indicator to make process of identifying initialization issues and load performance
- UI for making v3 themes and palettes, support for bundling v3 themes

145
biome.json Normal file
View file

@ -0,0 +1,145 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": ["**", "!!**/dist", "!!tools/emojis.json"]
},
"formatter": {
"enabled": true,
"indentStyle": "space"
},
"linter": {
"enabled": true,
"domains": {
"vue": "recommended"
},
"rules": {
"recommended": false,
"complexity": {
"noAdjacentSpacesInRegex": "error",
"noExtraBooleanCast": "error",
"noUselessCatch": "error",
"noUselessEscapeInRegex": "error"
},
"correctness": {
"noConstAssign": "error",
"noConstantCondition": "error",
"noEmptyCharacterClassInRegex": "error",
"noEmptyPattern": "error",
"noGlobalObjectCalls": "error",
"noInvalidBuiltinInstantiation": "error",
"noInvalidConstructorSuper": "error",
"noNonoctalDecimalEscape": "error",
"noPrecisionLoss": "error",
"noSelfAssign": "error",
"noSetterReturn": "error",
"noSwitchDeclarations": "error",
"noUndeclaredVariables": "error",
"noUnreachable": "error",
"noUnreachableSuper": "error",
"noUnsafeFinally": "error",
"noUnsafeOptionalChaining": "error",
"noUnusedLabels": "error",
"noUnusedPrivateClassMembers": "error",
"noUnusedVariables": "error",
"useIsNan": "error",
"useValidForDirection": "error",
"useValidTypeof": "error",
"useYield": "error"
},
"suspicious": {
"noAsyncPromiseExecutor": "error",
"noCatchAssign": "error",
"noClassAssign": "error",
"noCompareNegZero": "error",
"noConstantBinaryExpressions": "error",
"noControlCharactersInRegex": "error",
"noDebugger": "error",
"noDuplicateCase": "error",
"noDuplicateClassMembers": "error",
"noDuplicateElseIf": "error",
"noDuplicateObjectKeys": "error",
"noDuplicateParameters": "error",
"noEmptyBlockStatements": "error",
"noFallthroughSwitchClause": "error",
"noFunctionAssign": "error",
"noGlobalAssign": "error",
"noImportAssign": "error",
"noIrregularWhitespace": "error",
"noMisleadingCharacterClass": "error",
"noPrototypeBuiltins": "error",
"noRedeclare": "error",
"noShadowRestrictedNames": "error",
"noSparseArray": "error",
"noUnsafeNegation": "error",
"noUselessRegexBackrefs": "error",
"noWith": "error",
"useGetterReturn": "error"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
},
"globals": []
},
"overrides": [
{
"includes": ["**/*.spec.js", "test/fixtures/*.js"],
"javascript": {
"globals": [
"vi",
"describe",
"it",
"test",
"expect",
"before",
"beforeEach",
"after",
"afterEach"
]
}
},
{
"includes": ["**/*.vue"],
"linter": {
"rules": {
"style": {
"useConst": "off",
"useImportType": "off"
},
"correctness": {
"noUnusedVariables": "off",
"noUnusedImports": "off"
}
}
}
}
],
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": {
"level": "on",
"options": {
"groups": [
[":NODE:", ":PACKAGE:", "!src/**", "!@fortawesome/**"],
":BLANK_LINE:",
[":PATH:", "src/**"],
":BLANK_LINE:",
"@fortawesome/fontawesome-svg-core",
"@fortawesome/*"
]
}
}
}
}
}
}

View file

@ -1,5 +1,5 @@
import semver from 'semver'
import chalk from 'chalk'
import semver from 'semver'
import packageConfig from '../package.json' with { type: 'json' }
@ -7,8 +7,8 @@ var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
versionRequirement: packageConfig.engines.node,
},
]
export default function () {
@ -16,15 +16,22 @@ export default function () {
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
warnings.push(
mod.name +
': ' +
chalk.red(mod.currentVersion) +
' should be ' +
chalk.green(mod.versionRequirement),
)
}
}
if (warnings.length) {
console.warn(chalk.yellow('\nTo use this template, you must update following to modules:\n'))
console.warn(
chalk.yellow(
'\nTo use this template, you must update following to modules:\n',
),
)
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.warn(' ' + warning)

View file

@ -1,8 +1,8 @@
import childProcess from 'child_process'
export const getCommitHash = (() => {
const subst = "$Format:%h$"
if(!subst.match(/Format:/)) {
export const getCommitHash = () => {
const subst = '$Format:%h$'
if (!subst.match(/Format:/)) {
return subst
} else {
try {
@ -15,4 +15,4 @@ export const getCommitHash = (() => {
return 'UNKNOWN'
}
}
})
}

View file

@ -1,8 +1,8 @@
import serveStatic from 'serve-static'
import { resolve } from 'node:path'
import { cp } from 'node:fs/promises'
import { resolve } from 'node:path'
import serveStatic from 'serve-static'
const getPrefix = s => {
const getPrefix = (s) => {
const padEnd = s.endsWith('/') ? s : s + '/'
return padEnd.startsWith('/') ? padEnd : '/' + padEnd
}
@ -13,28 +13,31 @@ const copyPlugin = ({ inUrl, inFs }) => {
let copyTarget
const handler = serveStatic(inFs)
return [{
name: 'copy-plugin-serve',
apply: 'serve',
configureServer (server) {
server.middlewares.use(prefix, handler)
}
}, {
name: 'copy-plugin-build',
apply: 'build',
configResolved (config) {
copyTarget = resolve(config.root, config.build.outDir, subdir)
return [
{
name: 'copy-plugin-serve',
apply: 'serve',
configureServer(server) {
server.middlewares.use(prefix, handler)
},
},
closeBundle: {
order: 'post',
sequential: true,
async handler () {
console.log(`Copying '${inFs}' to ${copyTarget}...`)
await cp(inFs, copyTarget, { recursive: true })
console.log('Done.')
}
}
}]
{
name: 'copy-plugin-build',
apply: 'build',
configResolved(config) {
copyTarget = resolve(config.root, config.build.outDir, subdir)
},
closeBundle: {
order: 'post',
sequential: true,
async handler() {
console.info(`Copying '${inFs}' to ${copyTarget}...`)
await cp(inFs, copyTarget, { recursive: true })
console.info('Done.')
},
},
},
]
}
export default copyPlugin

View file

@ -1,21 +1,23 @@
import { resolve } from 'node:path'
import { access } from 'node:fs/promises'
import { languages, langCodeToCldrName } from '../src/i18n/languages.js'
import { resolve } from 'node:path'
import { languages } from '../src/i18n/languages.js'
const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/'
const specialAnnotationsLocale = {
ja_easy: 'ja'
ja_easy: 'ja',
}
const internalToAnnotationsLocale = (internal) => specialAnnotationsLocale[internal] || internal
const internalToAnnotationsLocale = (internal) =>
specialAnnotationsLocale[internal] || internal
// This gets all the annotations that are accessible (whose language
// can be chosen in the settings). Data for other languages are
// discarded because there is no way for it to be fetched.
const getAllAccessibleAnnotations = async (projectRoot) => {
const imports = (await Promise.all(
languages
.map(async lang => {
const imports = (
await Promise.all(
languages.map(async (lang) => {
const destLang = internalToAnnotationsLocale(lang)
const importModule = `${annotationsImportPrefix}${destLang}.json`
const importFile = resolve(projectRoot, 'node_modules', importModule)
@ -23,11 +25,14 @@ const getAllAccessibleAnnotations = async (projectRoot) => {
await access(importFile)
return `'${lang}': () => import('${importModule}')`
} catch (e) {
console.error(e)
return
}
})))
.filter(k => k)
.join(',\n')
}),
)
)
.filter((k) => k)
.join(',\n')
return `
export const annotationsLoader = {
@ -43,21 +48,21 @@ const emojisPlugin = () => {
let projectRoot
return {
name: 'emojis-plugin',
configResolved (conf) {
configResolved(conf) {
projectRoot = conf.root
},
resolveId (id) {
resolveId(id) {
if (id === emojiAnnotationsId) {
return emojiAnnotationsIdResolved
}
return null
},
async load (id) {
async load(id) {
if (id === emojiAnnotationsIdResolved) {
return await getAllAccessibleAnnotations(projectRoot)
}
return null
}
},
}
}

View file

@ -1,5 +1,5 @@
import { resolve } from 'node:path'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
const target = 'node_modules/msw/lib/mockServiceWorker.js'
@ -8,10 +8,10 @@ const mswPlugin = () => {
return {
name: 'msw-plugin',
apply: 'serve',
configResolved (conf) {
configResolved(conf) {
projectRoot = conf.root
},
configureServer (server) {
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
if (req.path === '/mockServiceWorker.js') {
const file = await readFile(resolve(projectRoot, target))
@ -21,7 +21,7 @@ const mswPlugin = () => {
next()
}
})
}
},
}
}

View file

@ -1,11 +1,12 @@
import { languages, langCodeToJsonName } from '../src/i18n/languages.js'
import { readFile } from 'node:fs/promises'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { langCodeToJsonName, languages } from '../src/i18n/languages.js'
const i18nDir = resolve(
dirname(dirname(fileURLToPath(import.meta.url))),
'src/i18n'
'src/i18n',
)
export const i18nFiles = languages.reduce((acc, lang) => {
@ -16,13 +17,15 @@ export const i18nFiles = languages.reduce((acc, lang) => {
}, {})
export const generateServiceWorkerMessages = async () => {
const msgArray = await Promise.all(Object.entries(i18nFiles).map(async ([lang, file]) => {
const fileContent = await readFile(file, 'utf-8')
const msg = {
notifications: JSON.parse(fileContent).notifications || {}
}
return [lang, msg]
}))
const msgArray = await Promise.all(
Object.entries(i18nFiles).map(async ([lang, file]) => {
const fileContent = await readFile(file, 'utf-8')
const msg = {
notifications: JSON.parse(fileContent).notifications || {},
}
return [lang, msg]
}),
)
return msgArray.reduce((acc, [lang, msg]) => {
acc[lang] = msg
return acc

View file

@ -1,9 +1,13 @@
import { fileURLToPath } from 'node:url'
import { dirname, resolve } from 'node:path'
import { readFile } from 'node:fs/promises'
import { build } from 'vite'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import * as esbuild from 'esbuild'
import { generateServiceWorkerMessages, i18nFiles } from './service_worker_messages.js'
import { build } from 'vite'
import {
generateServiceWorkerMessages,
i18nFiles,
} from './service_worker_messages.js'
const getSWMessagesAsText = async () => {
const messages = await generateServiceWorkerMessages()
@ -14,14 +18,10 @@ const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
const swEnvName = 'virtual:pleroma-fe/service_worker_env'
const swEnvNameResolved = '\0' + swEnvName
const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };`
const getProdSwEnv = ({ assets }) => `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
const getProdSwEnv = ({ assets }) =>
`self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
export const devSwPlugin = ({
swSrc,
swDest,
transformSW,
alias
}) => {
export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => {
const swFullSrc = resolve(projectRoot, swSrc)
const esbuildAlias = {}
Object.entries(alias).forEach(([source, dest]) => {
@ -31,9 +31,10 @@ export const devSwPlugin = ({
return {
name: 'dev-sw-plugin',
apply: 'serve',
configResolved (conf) {
configResolved() {
/* no-op */
},
resolveId (id) {
resolveId(id) {
const name = id.startsWith('/') ? id.slice(1) : id
if (name === swDest) {
return swFullSrc
@ -42,7 +43,7 @@ export const devSwPlugin = ({
}
return null
},
async load (id) {
async load(id) {
if (id === swFullSrc) {
return readFile(swFullSrc, 'utf-8')
} else if (id === swEnvNameResolved) {
@ -55,7 +56,7 @@ export const devSwPlugin = ({
* during dev, and firefox does not support ESM as service worker
* https://bugzilla.mozilla.org/show_bug.cgi?id=1360870
*/
async transform (code, id) {
async transform(code, id) {
if (id === swFullSrc && transformSW) {
const res = await esbuild.build({
entryPoints: [swSrc],
@ -63,52 +64,54 @@ export const devSwPlugin = ({
write: false,
outfile: 'sw-pleroma.js',
alias: esbuildAlias,
plugins: [{
name: 'vite-like-root-resolve',
setup (b) {
b.onResolve(
{ filter: new RegExp(/^\//) },
args => ({
path: resolve(projectRoot, args.path.slice(1))
})
)
}
}, {
name: 'sw-messages',
setup (b) {
b.onResolve(
{ filter: new RegExp('^' + swMessagesName + '$') },
args => ({
path: args.path,
namespace: 'sw-messages'
plugins: [
{
name: 'vite-like-root-resolve',
setup(b) {
b.onResolve({ filter: new RegExp(/^\//) }, (args) => ({
path: resolve(projectRoot, args.path.slice(1)),
}))
b.onLoad(
{ filter: /.*/, namespace: 'sw-messages' },
async () => ({
contents: await getSWMessagesAsText()
},
},
{
name: 'sw-messages',
setup(b) {
b.onResolve(
{ filter: new RegExp('^' + swMessagesName + '$') },
(args) => ({
path: args.path,
namespace: 'sw-messages',
}),
)
b.onLoad(
{ filter: /.*/, namespace: 'sw-messages' },
async () => ({
contents: await getSWMessagesAsText(),
}),
)
},
},
{
name: 'sw-env',
setup(b) {
b.onResolve(
{ filter: new RegExp('^' + swEnvName + '$') },
(args) => ({
path: args.path,
namespace: 'sw-env',
}),
)
b.onLoad({ filter: /.*/, namespace: 'sw-env' }, () => ({
contents: getDevSwEnv(),
}))
}
}, {
name: 'sw-env',
setup (b) {
b.onResolve(
{ filter: new RegExp('^' + swEnvName + '$') },
args => ({
path: args.path,
namespace: 'sw-env'
}))
b.onLoad(
{ filter: /.*/, namespace: 'sw-env' },
() => ({
contents: getDevSwEnv()
}))
}
}]
},
},
],
})
const text = res.outputFiles[0].text
return text
}
}
},
}
}
@ -118,16 +121,13 @@ export const devSwPlugin = ({
// however, we must compile the service worker to iife because of browser support.
// Run another vite build just for the service worker targeting iife at
// the end of the build.
export const buildSwPlugin = ({
swSrc,
swDest,
}) => {
export const buildSwPlugin = ({ swSrc, swDest }) => {
let config
return {
name: 'build-sw-plugin',
enforce: 'post',
apply: 'build',
configResolved (resolvedConfig) {
configResolved(resolvedConfig) {
config = {
define: resolvedConfig.define,
resolve: resolvedConfig.resolve,
@ -138,50 +138,50 @@ export const buildSwPlugin = ({
lib: {
entry: swSrc,
formats: ['iife'],
name: 'sw_pleroma'
name: 'sw_pleroma',
},
emptyOutDir: false,
rollupOptions: {
output: {
entryFileNames: swDest
}
}
entryFileNames: swDest,
},
},
},
configFile: false
configFile: false,
}
},
generateBundle: {
order: 'post',
sequential: true,
async handler (_, bundle) {
async handler(_, bundle) {
const assets = Object.keys(bundle)
.filter(name => !/\.map$/.test(name))
.map(name => '/' + name)
.filter((name) => !/\.map$/.test(name))
.map((name) => '/' + name)
config.plugins.push({
name: 'build-sw-env-plugin',
resolveId (id) {
resolveId(id) {
if (id === swEnvName) {
return swEnvNameResolved
}
return null
},
load (id) {
load(id) {
if (id === swEnvNameResolved) {
return getProdSwEnv({ assets })
}
return null
}
},
})
}
},
},
closeBundle: {
order: 'post',
sequential: true,
async handler () {
console.log('Building service worker for production')
async handler() {
console.info('Building service worker for production')
await build(config)
}
}
},
},
}
}
@ -191,9 +191,9 @@ const swMessagesNameResolved = '\0' + swMessagesName
export const swMessagesPlugin = () => {
return {
name: 'sw-messages-plugin',
resolveId (id) {
resolveId(id) {
if (id === swMessagesName) {
Object.values(i18nFiles).forEach(f => {
Object.values(i18nFiles).forEach((f) => {
this.addWatchFile(f)
})
return swMessagesNameResolved
@ -201,11 +201,11 @@ export const swMessagesPlugin = () => {
return null
}
},
async load (id) {
async load(id) {
if (id === swMessagesNameResolved) {
return await getSWMessagesAsText()
}
return null
}
},
}
}

View file

@ -1,22 +1,21 @@
import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json' }
import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with {
type: 'json',
}
import fs from 'fs'
Object.keys(emojis)
.map(k => {
emojis[k].map(e => {
delete e.unicode_version
delete e.emoji_version
delete e.skin_tone_support_unicode_version
})
Object.keys(emojis).map((k) => {
emojis[k].map((e) => {
delete e.unicode_version
delete e.emoji_version
delete e.skin_tone_support_unicode_version
})
})
const res = {}
Object.keys(emojis)
.map(k => {
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
res[groupId] = emojis[k]
})
Object.keys(emojis).map((k) => {
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
res[groupId] = emojis[k]
})
console.info('Updating emojis...')
fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res))

View file

@ -1 +0,0 @@
Display counter for status action buttons when they are on the menu

View file

@ -0,0 +1 @@
fixed being unable to set actor type from profile page

View file

@ -1 +0,0 @@
Added support for Akkoma and IceShrimp.NET backend

View file

@ -1,2 +0,0 @@
Add arithmetic blend ISS function

View file

@ -1 +0,0 @@
Add support for detachable scrollTop button

View file

@ -1 +0,0 @@
Fix bookmark button alignment in the extra actions menu

View file

@ -1 +0,0 @@
Compatibility with stricter CSP (Akkoma backend)

View file

@ -0,0 +1 @@
Add playwright E2E-tests with an optional docker-based backend

1
changelog.d/e2e.skip Normal file
View file

@ -0,0 +1 @@
fix e2e

View file

@ -1 +0,0 @@
Synchronized mutes, advanced mute control (regexp, expiry, naming)

View file

@ -1 +0,0 @@
Fix error styling for user profiles

View file

@ -1 +0,0 @@
Cache assets and emojis with service worker

View file

@ -1 +0,0 @@
Indicate currently active V3 theme as a body element class

View file

@ -1 +0,0 @@
Unify show/hide content buttons

57
docker-compose.e2e.yml Normal file
View file

@ -0,0 +1,57 @@
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: pleroma
POSTGRES_PASSWORD: pleroma
POSTGRES_DB: pleroma
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pleroma -d pleroma"]
interval: 2s
timeout: 2s
retries: 30
pleroma:
image: ${PLEROMA_IMAGE:-git.pleroma.social:5050/pleroma/pleroma:stable}
environment:
DB_USER: pleroma
DB_PASS: pleroma
DB_NAME: pleroma
DB_HOST: db
DB_PORT: 5432
DOMAIN: localhost
INSTANCE_NAME: Pleroma E2E
ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
NOTIFY_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
E2E_ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
depends_on:
db:
condition: service_healthy
volumes:
- ./docker/pleroma/entrypoint.e2e.sh:/opt/pleroma/entrypoint.e2e.sh:ro
entrypoint: ["/bin/ash", "/opt/pleroma/entrypoint.e2e.sh"]
healthcheck:
# NOTE: "localhost" may resolve to ::1 in some images (IPv6) while Pleroma only
# listens on IPv4 in this container. Use 127.0.0.1 to avoid false negatives.
test: ["CMD-SHELL", "test -f /var/lib/pleroma/.e2e_seeded && wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null || exit 1"]
interval: 5s
timeout: 3s
retries: 60
e2e:
build:
context: .
dockerfile: docker/e2e/Dockerfile.e2e
depends_on:
pleroma:
condition: service_healthy
environment:
CI: "1"
VITE_PROXY_TARGET: http://pleroma:4000
VITE_PROXY_ORIGIN: http://localhost:4000
E2E_BASE_URL: http://localhost:8080
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
command: ["yarn", "e2e:pw"]

16
docker/e2e/Dockerfile.e2e Normal file
View file

@ -0,0 +1,16 @@
FROM mcr.microsoft.com/playwright:v1.57.0-jammy
WORKDIR /app
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
RUN npm install -g yarn@1.22.22
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
COPY . .
ENV CI=1
CMD ["yarn", "e2e:pw"]

View file

@ -0,0 +1,71 @@
#!/bin/ash
set -eu
SEED_SENTINEL_PATH="/var/lib/pleroma/.e2e_seeded"
CONFIG_OVERRIDE_PATH="/var/lib/pleroma/config.exs"
echo "-- Waiting for database..."
while ! pg_isready -U "${DB_USER:-pleroma}" -d "postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma}" -t 1; do
sleep 1s
done
echo "-- Writing E2E config overrides..."
cat > "$CONFIG_OVERRIDE_PATH" <<'EOF'
import Config
config :pleroma, Pleroma.Captcha,
enabled: false
config :pleroma, :instance,
registrations_open: true,
account_activation_required: false,
approval_required: false
EOF
echo "-- Running migrations..."
/opt/pleroma/bin/pleroma_ctl migrate
echo "-- Starting!"
/opt/pleroma/bin/pleroma start &
PLEROMA_PID="$!"
cleanup() {
if [ -n "${PLEROMA_PID:-}" ] && kill -0 "$PLEROMA_PID" 2>/dev/null; then
kill -TERM "$PLEROMA_PID"
wait "$PLEROMA_PID" || true
fi
}
trap cleanup INT TERM
echo "-- Waiting for API..."
api_ok="false"
for _i in $(seq 1 120); do
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
api_ok="true"
break
fi
sleep 1s
done
if [ "$api_ok" != "true" ]; then
echo "Timed out waiting for Pleroma API to become available"
exit 1
fi
if [ ! -f "$SEED_SENTINEL_PATH" ]; then
if [ -n "${E2E_ADMIN_USERNAME:-}" ] && [ -n "${E2E_ADMIN_PASSWORD:-}" ] && [ -n "${E2E_ADMIN_EMAIL:-}" ]; then
echo "-- Seeding admin user (${E2E_ADMIN_USERNAME})..."
if ! /opt/pleroma/bin/pleroma_ctl user new "$E2E_ADMIN_USERNAME" "$E2E_ADMIN_EMAIL" --admin --password "$E2E_ADMIN_PASSWORD" -y; then
echo "-- User already exists (or creation failed), ensuring admin + confirmed..."
/opt/pleroma/bin/pleroma_ctl user set "$E2E_ADMIN_USERNAME" --admin --confirmed
fi
else
echo "-- Skipping admin seeding (missing E2E_ADMIN_* env)"
fi
touch "$SEED_SENTINEL_PATH"
fi
wait "$PLEROMA_PID"

View file

@ -1,37 +1,34 @@
import vue from "eslint-plugin-vue";
import js from "@eslint/js";
import globals from "globals";
import js from '@eslint/js'
import { defineConfig, globalIgnores } from 'eslint/config'
import vue from 'eslint-plugin-vue'
import globals from 'globals'
export default [
export default defineConfig([
...vue.configs['flat/recommended'],
js.configs.recommended,
globalIgnores(['**/*.js', 'build/', 'dist/', 'config/']),
{
files: ["**/*.js", "**/*.mjs", "**/*.vue"],
ignores: ["build/*.js", "config/*.js"],
files: ['src/**/*.vue'],
plugins: { js },
extends: ['js/recommended'],
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
sourceType: 'module',
parserOptions: {
parser: "@babel/eslint-parser",
parser: '@babel/eslint-parser',
},
globals: {
...globals.browser,
...globals.vitest,
...globals.chai,
...globals.commonjs,
...globals.serviceworker
}
...globals.serviceworker,
},
},
rules: {
'arrow-parens': 0,
'generator-star-spacing': 0,
'no-debugger': 0,
'vue/require-prop-types': 0,
'vue/multi-word-component-names': 0,
}
}
]
},
},
])

View file

@ -11,14 +11,12 @@
<link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" />
<!-- putting styles here to avoid having to wait for styles to load up -->
<link rel="stylesheet" id="splashscreen" href="/static/splash.css" />
<link rel="stylesheet" id="pleroma-eager-styles" type="text/css" href="/static/empty.css" />
<link rel="stylesheet" id="pleroma-lazy-styles" type="text/css" href="/static/empty.css" />
<link rel="stylesheet" id="theme-holder" type="text/css" href="/static/empty.css" />
<link rel="stylesheet" id="custom-styles-holder" type="text/css" href="/static/empty.css" />
<!--server-generated-meta-->
</head>
<body>
<noscript>To use Pleroma, please enable JavaScript.</noscript>
<div id="splash">
<div id="splash" class="initial-hidden">
<!-- we are hiding entire graphic so no point showing credit -->
<div aria-hidden="true" id="splash-credit">
Art by pipivovott

View file

@ -1,6 +1,6 @@
{
"name": "pleroma_fe",
"version": "2.7.1",
"version": "2.10.0",
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
"private": false,
@ -10,36 +10,39 @@
"unit": "node build/update-emoji.js && vitest --run",
"unit-ci": "node build/update-emoji.js && vitest --run --browser.headless",
"unit:watch": "node build/update-emoji.js && vitest",
"e2e": "node test/e2e/runner.js",
"e2e:pw": "playwright test --config test/e2e-playwright/playwright.config.mjs",
"e2e": "sh ./tools/e2e/run.sh",
"test": "yarn run unit && yarn run e2e",
"stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
"lint": "eslint src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix src test/unit/specs test/e2e/specs"
"ci-biome": "yarn exec biome check",
"ci-eslint": "yarn exec eslint",
"ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
"lint": "yarn ci-biome; yarn ci-eslint; yarn ci-stylelint",
"lint-fix": "yarn exec eslint --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write"
},
"dependencies": {
"@babel/runtime": "7.27.1",
"@babel/runtime": "7.28.4",
"@chenfengyuan/vue-qrcode": "2.0.0",
"@fortawesome/fontawesome-svg-core": "6.7.2",
"@fortawesome/free-regular-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/vue-fontawesome": "3.0.8",
"@floatingghost/pinch-zoom-element": "1.3.1",
"@fortawesome/fontawesome-svg-core": "7.1.0",
"@fortawesome/free-regular-svg-icons": "7.1.0",
"@fortawesome/free-solid-svg-icons": "7.1.0",
"@fortawesome/vue-fontawesome": "3.1.2",
"@kazvmoe-infra/pinch-zoom-element": "1.3.0",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.6.22",
"@vuelidate/core": "2.0.3",
"@vuelidate/validators": "2.0.4",
"@web3-storage/parse-link-header": "^3.1.0",
"body-scroll-lock": "3.1.5",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
"cropperjs": "2.0.0",
"cropperjs": "2.0.1",
"escape-html": "1.0.3",
"globals": "^16.0.0",
"hash-sum": "^2.0.0",
"js-cookie": "3.0.5",
"localforage": "1.10.0",
"parse-link-header": "2.0.0",
"phoenix": "1.7.21",
"phoenix": "1.8.1",
"pinia": "^3.0.0",
"punycode.js": "2.3.1",
"qrcode": "1.5.4",
@ -47,63 +50,63 @@
"url": "0.11.4",
"utf8": "3.0.0",
"uuid": "11.1.0",
"vue": "3.5.17",
"vue": "3.5.22",
"vue-i18n": "11",
"vue-router": "4.5.1",
"vue-router": "4.6.4",
"vue-virtual-scroller": "^2.0.0-beta.7",
"vuex": "4.1.0"
},
"devDependencies": {
"@babel/core": "7.27.1",
"@babel/eslint-parser": "7.27.1",
"@babel/plugin-transform-runtime": "7.27.1",
"@babel/preset-env": "7.27.2",
"@babel/register": "7.27.1",
"@babel/core": "7.28.5",
"@babel/eslint-parser": "7.28.5",
"@babel/plugin-transform-runtime": "7.28.5",
"@babel/preset-env": "7.28.5",
"@babel/register": "7.28.3",
"@biomejs/biome": "2.3.11",
"@ungap/event-target": "0.2.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vitest/browser": "^3.0.7",
"@vitest/ui": "^3.0.7",
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.4.0",
"@vue/compiler-sfc": "3.5.17",
"@vue/babel-plugin-jsx": "1.5.0",
"@vue/compiler-sfc": "3.5.22",
"@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.21",
"babel-plugin-lodash": "3.3.4",
"chai": "5.2.0",
"chalk": "5.4.1",
"chai": "5.3.3",
"chalk": "5.6.2",
"chromedriver": "135.0.4",
"connect-history-api-fallback": "2.0.0",
"cross-spawn": "7.0.6",
"custom-event-polyfill": "1.0.7",
"eslint": "9.26.0",
"vue-eslint-parser": "10.1.3",
"eslint": "9.39.2",
"eslint-config-standard": "17.1.0",
"eslint-formatter-friendly": "7.0.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-n": "17.18.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-n": "17.23.1",
"eslint-plugin-promise": "7.2.1",
"eslint-plugin-vue": "10.1.0",
"eslint-plugin-vue": "10.6.2",
"eventsource-polyfill": "0.9.6",
"express": "5.1.0",
"function-bind": "1.1.2",
"http-proxy-middleware": "3.0.5",
"iso-639-1": "3.1.5",
"lodash": "4.17.21",
"msw": "2.10.2",
"nightwatch": "3.12.1",
"playwright": "1.52.0",
"postcss": "8.5.3",
"msw": "2.10.5",
"nightwatch": "3.12.2",
"playwright": "1.57.0",
"postcss": "8.5.6",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6",
"sass": "1.89.2",
"sass": "1.93.2",
"selenium-server": "3.141.59",
"semver": "7.7.2",
"semver": "7.7.3",
"serve-static": "2.2.0",
"shelljs": "0.10.0",
"sinon": "20.0.0",
"sinon-chai": "4.0.0",
"stylelint": "16.19.1",
"sinon-chai": "4.0.1",
"stylelint": "16.25.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^16.0.0",
"stylelint-config-recommended-scss": "^14.0.0",
@ -112,7 +115,8 @@
"vite": "^6.1.0",
"vite-plugin-eslint2": "^5.0.3",
"vite-plugin-stylelint": "^6.0.0",
"vitest": "^3.0.7"
"vitest": "^3.0.7",
"vue-eslint-parser": "10.2.0"
},
"type": "module",
"engines": {

View file

@ -1,7 +1,5 @@
import autoprefixer from 'autoprefixer'
export default {
plugins: [
autoprefixer
]
plugins: [autoprefixer],
}

View file

@ -25,7 +25,7 @@
"sidebarRight": false,
"subjectLineBehavior": "email",
"theme": null,
"style": null,
"palette": null,
"style": "BreezyDX",
"palette": "sigsegv2",
"webPushNotifications": false
}

View file

@ -1 +0,0 @@
// nothing here //

View file

@ -1,6 +1,26 @@
{
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"pleroma-dark": [
"Pleroma Dark",
"#121a24",
"#182230",
"#b9b9ba",
"#d8a070",
"#d31014",
"#0fa00f",
"#0095ff",
"#ffa500"
],
"pleroma-light": [
"Pleroma Light",
"#f2f4f6",
"#dbe0e8",
"#304055",
"#f86f0f",
"#d31014",
"#0fa00f",
"#0095ff",
"#ffa500"
],
"classic-dark": {
"name": "Classic Dark",
"bg": "#161c20",
@ -12,8 +32,28 @@
"cBlue": "#0095ff",
"cOrange": "#ffa500"
},
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
"pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"],
"bird": [
"Bird",
"#f8fafd",
"#e6ecf0",
"#14171a",
"#0084b8",
"#e0245e",
"#17bf63",
"#1b95e0",
"#fab81e"
],
"pleroma-amoled": [
"Pleroma Dark AMOLED",
"#000000",
"#111111",
"#b0b0b1",
"#d8a070",
"#aa0000",
"#0fa00f",
"#0095ff",
"#d59500"
],
"tomorrow-night": {
"name": "Tomorrow Night",
"bg": "#1d1f21",
@ -36,8 +76,28 @@
"cGreen": "#50FA7B",
"cOrange": "#FFB86C"
},
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
"monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
"ir-black": [
"Ir Black",
"#000000",
"#242422",
"#b5b3aa",
"#ff6c60",
"#FF6C60",
"#A8FF60",
"#96CBFE",
"#FFFFB6"
],
"monokai": [
"Monokai",
"#272822",
"#383830",
"#f8f8f2",
"#f92672",
"#F92672",
"#a6e22e",
"#66d9ef",
"#f4bf75"
],
"purple-stream": {
"name": "Purple stream",
"bg": "#17171A",

View file

@ -5,15 +5,14 @@ body {
#splash {
--scale: 1;
width: 100vw;
height: 100vh;
display: grid;
grid-template-rows: auto;
grid-template-columns: auto;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
place-items: center;
flex-direction: column;
background: #0f161e;
font-family: sans-serif;
@ -21,13 +20,20 @@ body {
position: absolute;
z-index: 9999;
font-size: calc(1vw + 1vh + 1vmin);
opacity: 1;
transition: opacity 500ms ease-out 2s;
}
#splash.hidden,
#splash.initial-hidden {
opacity: 0;
}
#splash-credit {
position: absolute;
font-size: 14px;
bottom: 16px;
right: 16px;
font-size: 1em;
bottom: 1em;
right: 1em;
}
#splash-container {
@ -59,16 +65,17 @@ body {
z-index: 2;
grid-template-rows: repeat(8, 1fr);
grid-template-columns: repeat(5, 1fr);
grid-template-areas: "P P . L L"
"P P . L L"
"P P . L L"
"P P . L L"
"P P . . ."
"P P . . ."
"P P . E E"
"P P . E E";
grid-template-areas:
"P P . L L"
"P P . L L"
"P P . L L"
"P P . L L"
"P P . . ."
"P P . . ."
"P P . E E"
"P P . E E";
--logoChunkSize: calc(2em * 0.5 * var(--scale))
--logoChunkSize: calc(2em * 0.5 * var(--scale));
}
.chunk {
@ -78,7 +85,7 @@ body {
#chunk-P {
grid-area: P;
border-top-left-radius: calc(var(--logoChunkSize) / 2);
border-top-left-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-L {

View file

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

View file

@ -1,34 +1,36 @@
import UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_panel.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue'
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { getOrCreateServiceWorker } from './services/sw/sw'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
import { defineAsyncComponent } from 'vue'
import { useShoutStore } from './stores/shout'
import { useInterfaceStore } from './stores/interface'
import { throttle } from 'lodash'
import { defineAsyncComponent } from 'vue'
import { mapGetters } from 'vuex'
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import NavPanel from './components/nav_panel/nav_panel.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import UserPanel from './components/user_panel/user_panel.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import { getOrCreateServiceWorker } from './services/sw/sw'
import { windowHeight, windowWidth } from './services/window_utils/window_utils'
import { useInterfaceStore } from './stores/interface'
import { useShoutStore } from './stores/shout'
export default {
name: 'app',
components: {
UserPanel,
NavPanel,
Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
Notifications: defineAsyncComponent(
() => import('./components/notifications/notifications.vue'),
),
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
@ -38,29 +40,33 @@ export default {
MobilePostStatusButton,
MobileNav,
DesktopNav,
SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')),
SettingsModal: defineAsyncComponent(
() => import('./components/settings_modal/settings_modal.vue'),
),
UpdateNotification: defineAsyncComponent(
() => import('./components/update_notification/update_notification.vue'),
),
UserReportingModal,
PostStatusModal,
EditStatusModal,
StatusHistoryModal,
GlobalNoticeList
GlobalNoticeList,
},
data: () => ({
mobileActivePanel: 'timeline'
mobileActivePanel: 'timeline',
}),
watch: {
themeApplied () {
themeApplied() {
this.removeSplash()
},
currentTheme () {
currentTheme() {
this.setThemeBodyClass()
},
layoutType () {
layoutType() {
document.getElementById('modal').classList = ['-' + this.layoutType]
}
},
},
created () {
created() {
// Load the locale from the storage
const val = this.$store.getters.mergedConfig.interfaceLanguage
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
@ -70,7 +76,7 @@ export default {
this.updateScrollState = throttle(this.scrollHandler, 200)
this.updateMobileState = throttle(this.resizeHandler, 200)
},
mounted () {
mounted() {
window.addEventListener('resize', this.updateMobileState)
this.scrollParent.addEventListener('scroll', this.updateScrollState)
@ -80,108 +86,145 @@ export default {
}
getOrCreateServiceWorker()
},
unmounted () {
unmounted() {
window.removeEventListener('resize', this.updateMobileState)
this.scrollParent.removeEventListener('scroll', this.updateScrollState)
},
computed: {
themeApplied () {
themeApplied() {
return useInterfaceStore().themeApplied
},
currentTheme () {
currentTheme() {
if (useInterfaceStore().styleDataUsed) {
const styleMeta = useInterfaceStore().styleDataUsed.find(x => x.component === '@meta')
const styleMeta = useInterfaceStore().styleDataUsed.find(
(x) => x.component === '@meta',
)
if (styleMeta !== undefined) {
return styleMeta.directives.name.replaceAll(" ", "-").toLowerCase()
return styleMeta.directives.name.replaceAll(' ', '-').toLowerCase()
}
}
return 'stock'
},
layoutModalClass () {
layoutModalClass() {
return '-' + this.layoutType
},
classes () {
classes() {
return [
{
'-reverse': this.reverseLayout,
'-no-sticky-headers': this.noSticky,
'-has-new-post-button': this.newPostButtonShown
'-has-new-post-button': this.newPostButtonShown,
},
'-' + this.layoutType
'-' + this.layoutType,
]
},
navClasses () {
navClasses() {
const { navbarColumnStretch } = this.$store.getters.mergedConfig
return [
'-' + this.layoutType,
...(navbarColumnStretch ? ['-column-stretch'] : [])
...(navbarColumnStretch ? ['-column-stretch'] : []),
]
},
currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image },
instanceBackground () {
currentUser() {
return this.$store.state.users.currentUser
},
userBackground() {
return this.currentUser.background_image
},
instanceBackground() {
return this.mergedConfig.hideInstanceWallpaper
? null
: this.$store.state.instance.background
},
background () { return this.userBackground || this.instanceBackground },
bgStyle () {
background() {
return this.userBackground || this.instanceBackground
},
bgStyle() {
if (this.background) {
return {
'--body-background-image': `url(${this.background})`
'--body-background-image': `url(${this.background})`,
}
}
},
shout () { return useShoutStore().joined },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
shout() {
return useShoutStore().joined
},
suggestionsEnabled() {
return this.$store.state.instance.suggestionsEnabled
},
showInstanceSpecificPanel() {
return (
this.$store.state.instance.showInstanceSpecificPanel &&
!this.$store.getters.mergedConfig.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent
)
},
isChats () {
isChats() {
return this.$route.name === 'chat' || this.$route.name === 'chats'
},
isListEdit () {
isListEdit() {
return this.$route.name === 'lists-edit'
},
newPostButtonShown () {
newPostButtonShown() {
if (this.isChats) return false
if (this.isListEdit) return false
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
return (
this.$store.getters.mergedConfig.alwaysShowNewPostButton ||
this.layoutType === 'mobile'
)
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
editingAvailable () { return this.$store.state.instance.editingAvailable },
shoutboxPosition () {
showFeaturesPanel() {
return this.$store.state.instance.showFeaturesPanel
},
editingAvailable() {
return this.$store.state.instance.editingAvailable
},
shoutboxPosition() {
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
},
hideShoutbox () {
hideShoutbox() {
return this.$store.getters.mergedConfig.hideShoutbox
},
layoutType () { return useInterfaceStore().layoutType },
privateMode () { return this.$store.state.instance.private },
reverseLayout () {
const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
layoutType() {
return useInterfaceStore().layoutType
},
privateMode() {
return this.$store.state.instance.private
},
reverseLayout() {
const { thirdColumnMode, sidebarRight: reverseSetting } =
this.$store.getters.mergedConfig
if (this.layoutType !== 'wide') {
return reverseSetting
} else {
return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting
return thirdColumnMode === 'notifications'
? reverseSetting
: !reverseSetting
}
},
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
scrollParent () { return window; /* this.$refs.appContentRef */ },
...mapGetters(['mergedConfig'])
noSticky() {
return this.$store.getters.mergedConfig.disableStickyHeaders
},
showScrollbars() {
return this.$store.getters.mergedConfig.showScrollbars
},
scrollParent() {
return window /* this.$refs.appContentRef */
},
...mapGetters(['mergedConfig']),
},
methods: {
resizeHandler () {
resizeHandler() {
useInterfaceStore().setLayoutWidth(windowWidth())
useInterfaceStore().setLayoutHeight(windowHeight())
},
scrollHandler () {
const scrollPosition = this.scrollParent === window ? window.scrollY : this.scrollParent.scrollTop
scrollHandler() {
const scrollPosition =
this.scrollParent === window
? window.scrollY
: this.scrollParent.scrollTop
if (scrollPosition != 0) {
this.$refs.appContentRef.classList.add(['-scrolled'])
@ -189,10 +232,10 @@ export default {
this.$refs.appContentRef.classList.remove(['-scrolled'])
}
},
setThemeBodyClass () {
setThemeBodyClass() {
const themeName = this.currentTheme
const classList = Array.from(document.body.classList)
const oldTheme = classList.filter(c => c.startsWith('theme-'))
const oldTheme = classList.filter((c) => c.startsWith('theme-'))
if (themeName !== null && themeName !== '') {
const newTheme = `theme-${themeName.toLowerCase()}`
@ -208,14 +251,19 @@ export default {
document.body.classList.remove(...oldTheme)
}
},
removeSplash () {
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
removeSplash() {
document.querySelector('#status').textContent = this.$t(
'splash.fun_' + Math.ceil(Math.random() * 4),
)
const splashscreenRoot = document.querySelector('#splash')
splashscreenRoot.addEventListener('transitionend', () => {
splashscreenRoot.remove()
})
setTimeout(() => {
splashscreenRoot.remove() // forcibly remove it, should fix my plasma browser widget t. HJ
}, 600)
splashscreenRoot.classList.add('hidden')
document.querySelector('#app').classList.remove('hidden')
}
}
},
},
}

View file

@ -3,7 +3,7 @@
@use "panel";
@import '@fortawesome/fontawesome-svg-core/styles.css';
@import '@floatingghost/pinch-zoom-element/dist/pinch-zoom.css';
@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
:root {
--status-margin: 0.75em;
@ -21,7 +21,7 @@
}
html {
font-size: var(--textSize, 14px);
font-size: var(--textSize, 1rem);
--navbar-height: var(--navbarSize, 3.5rem);
--emoji-size: var(--emojiSize, 32px);
@ -382,6 +382,10 @@ nav {
font-family: sans-serif;
font-family: var(--font);
&.-transparent {
backdrop-filter: blur(0.125em) contrast(60%);
}
&::-moz-focus-inner {
border: none;
}
@ -509,6 +513,12 @@ nav {
}
}
label {
&.-disabled {
color: var(--textFaint);
}
}
input,
textarea {
border: none;
@ -525,6 +535,10 @@ textarea {
height: unset;
}
&::placeholder {
color: var(--textFaint)
}
--_padding: 0.5em;
border: none;
@ -545,6 +559,10 @@ textarea {
&[disabled="disabled"],
&.disabled {
cursor: not-allowed;
color: var(--textFaint);
/* stylelint-disable-next-line declaration-no-important */
background-color: transparent !important;
}
&[type="range"] {
@ -570,6 +588,8 @@ textarea {
& + label::before {
opacity: 0.5;
}
background-color: var(--background);
}
+ label::before {
@ -669,7 +689,8 @@ option {
list-style: none;
display: grid;
grid-auto-flow: row dense;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
grid-gap: 0.5em;
li {
border: 1px solid var(--border);
@ -679,11 +700,6 @@ option {
}
}
.btn-block {
display: block;
width: 100%;
}
.btn-group {
position: relative;
display: inline-flex;
@ -695,7 +711,6 @@ option {
--_roundness-right: 0;
position: relative;
flex: 1 1 auto;
}
> *:first-child,
@ -742,17 +757,15 @@ option {
}
&.-dot {
min-height: 8px;
max-height: 8px;
min-width: 8px;
max-width: 8px;
padding: 0;
min-height: 0.6em;
max-height: 0.6em;
min-width: 0.6em;
max-width: 0.6em;
left: calc(50% + 0.5em);
top: calc(50% - 1em);
line-height: 0;
font-size: 0;
left: calc(50% - 4px);
top: calc(50% - 4px);
margin-left: 6px;
margin-top: -6px;
padding: 0;
margin: 0;
}
&.-counter {
@ -783,12 +796,6 @@ option {
color: var(--text);
}
.visibility-notice {
padding: 0.5em;
border: 1px solid var(--textFaint);
border-radius: var(--roundness);
}
.notice-dismissible {
padding-right: 4rem;
position: relative;
@ -936,12 +943,7 @@ option {
#splash {
pointer-events: none;
transition: opacity 0.5s;
opacity: 1;
&.hidden {
opacity: 0;
}
// transition: opacity 0.5s;
#status {
&.css-ok {
@ -1080,7 +1082,7 @@ option {
scale: 1.0063 0.9938;
translate: 0 -10%;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-in-ou;
animation-timing-function: ease-in-out;
}
90% {

View file

@ -1,29 +1,39 @@
/* global process */
import vClickOutside from 'click-outside-vue3'
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import vClickOutside from 'click-outside-vue3'
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import { config } from '@fortawesome/fontawesome-svg-core';
import { config } from '@fortawesome/fontawesome-svg-core'
import {
FontAwesomeIcon,
FontAwesomeLayers,
} from '@fortawesome/vue-fontawesome'
config.autoAddCss = false
import App from '../App.vue'
import routes from './routes'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
import { useOAuthStore } from 'src/stores/oauth'
import { useI18nStore } from 'src/stores/i18n'
import { useInterfaceStore } from 'src/stores/interface'
import {
instanceDefaultConfig,
staticOrApiConfigDefault,
} from 'src/modules/default_config_state.js'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useAuthFlowStore } from 'src/stores/auth_flow'
import { useI18nStore } from 'src/stores/i18n'
import { useInterfaceStore } from 'src/stores/interface'
import { useOAuthStore } from 'src/stores/oauth'
import App from '../App.vue'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
import { applyConfig } from '../services/style_setter/style_setter.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
import {
windowHeight,
windowWidth,
} from '../services/window_utils/window_utils'
import routes from './routes'
let staticInitialResults = null
@ -32,7 +42,9 @@ const parsedInitialResults = () => {
return null
}
if (!staticInitialResults) {
staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent)
staticInitialResults = JSON.parse(
document.getElementById('initial-results').textContent,
)
}
return staticInitialResults
}
@ -54,7 +66,7 @@ const preloadFetch = async (request) => {
return {
ok: true,
json: () => requestData,
text: () => requestData
text: () => requestData,
}
}
@ -66,17 +78,35 @@ const getInstanceConfig = async ({ store }) => {
const textlimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'pleromaExtensionsAvailable', value: data.pleroma })
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required })
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0 })
store.dispatch('setInstanceOption', {
name: 'pleromaExtensionsAvailable',
value: data.pleroma,
})
store.dispatch('setInstanceOption', {
name: 'textlimit',
value: textlimit,
})
store.dispatch('setInstanceOption', {
name: 'accountApprovalRequired',
value: data.approval_required,
})
store.dispatch('setInstanceOption', {
name: 'birthdayRequired',
value: !!data.pleroma?.metadata.birthday_required,
})
store.dispatch('setInstanceOption', {
name: 'birthdayMinAge',
value: data.pleroma?.metadata.birthday_min_age || 0,
})
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
store.dispatch('setInstanceOption', {
name: 'vapidPublicKey',
value: vapidPublicKey,
})
}
} else {
throw (res)
throw res
}
} catch (error) {
console.error('Could not load instance config, potentially fatal')
@ -93,10 +123,12 @@ const getBackendProvidedConfig = async () => {
const data = await res.json()
return data.pleroma_fe
} else {
throw (res)
throw res
}
} catch (error) {
console.error('Could not load backend-provided frontend config, potentially fatal')
console.error(
'Could not load backend-provided frontend config, potentially fatal',
)
console.error(error)
}
}
@ -107,7 +139,7 @@ const getStaticConfig = async () => {
if (res.ok) {
return res.json()
} else {
throw (res)
throw res
}
} catch (error) {
console.warn('Failed to load static/config.json, continuing without it.')
@ -130,50 +162,15 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
}
const copyInstanceOption = (name) => {
store.dispatch('setInstanceOption', { name, value: config[name] })
if (typeof config[name] !== 'undefined') {
store.dispatch('setInstanceOption', { name, value: config[name] })
}
}
copyInstanceOption('theme')
copyInstanceOption('style')
copyInstanceOption('palette')
copyInstanceOption('embeddedToS')
copyInstanceOption('nsfwCensorImage')
copyInstanceOption('background')
copyInstanceOption('hidePostStats')
copyInstanceOption('hideBotIndication')
copyInstanceOption('hideUserStats')
copyInstanceOption('hideFilteredStatuses')
copyInstanceOption('logo')
Object.keys(staticOrApiConfigDefault).forEach(copyInstanceOption)
Object.keys(instanceDefaultConfig).forEach(copyInstanceOption)
store.dispatch('setInstanceOption', {
name: 'logoMask',
value: typeof config.logoMask === 'undefined'
? true
: config.logoMask
})
store.dispatch('setInstanceOption', {
name: 'logoMargin',
value: typeof config.logoMargin === 'undefined'
? 0
: config.logoMargin
})
copyInstanceOption('logoLeft')
useAuthFlowStore().setInitialStrategy(config.loginMethod)
copyInstanceOption('redirectRootNoLogin')
copyInstanceOption('redirectRootLogin')
copyInstanceOption('showInstanceSpecificPanel')
copyInstanceOption('minimalScopesMode')
copyInstanceOption('hideMutedPosts')
copyInstanceOption('collapseMessageWithSubject')
copyInstanceOption('scopeCopy')
copyInstanceOption('subjectLineBehavior')
copyInstanceOption('postContentType')
copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight')
}
const getTOS = async ({ store }) => {
@ -183,7 +180,7 @@ const getTOS = async ({ store }) => {
const html = await res.text()
store.dispatch('setInstanceOption', { name: 'tos', value: html })
} else {
throw (res)
throw res
}
} catch (e) {
console.warn("Can't load TOS\n", e)
@ -195,9 +192,12 @@ const getInstancePanel = async ({ store }) => {
const res = await preloadFetch('/instance/panel.html')
if (res.ok) {
const html = await res.text()
store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
store.dispatch('setInstanceOption', {
name: 'instanceSpecificPanelContent',
value: html,
})
} else {
throw (res)
throw res
}
} catch (e) {
console.warn("Can't load instance panel\n", e)
@ -209,25 +209,27 @@ const getStickers = async ({ store }) => {
const res = await window.fetch('/static/stickers.json')
if (res.ok) {
const values = await res.json()
const stickers = (await Promise.all(
Object.entries(values).map(async ([name, path]) => {
const resPack = await window.fetch(path + 'pack.json')
let meta = {}
if (resPack.ok) {
meta = await resPack.json()
}
return {
pack: name,
path,
meta
}
})
)).sort((a, b) => {
const stickers = (
await Promise.all(
Object.entries(values).map(async ([name, path]) => {
const resPack = await window.fetch(path + 'pack.json')
let meta = {}
if (resPack.ok) {
meta = await resPack.json()
}
return {
pack: name,
path,
meta,
}
}),
)
).sort((a, b) => {
return a.meta.title.localeCompare(b.meta.title)
})
store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
} else {
throw (res)
throw res
}
} catch (e) {
console.warn("Can't load stickers\n", e)
@ -237,13 +239,19 @@ const getStickers = async ({ store }) => {
const getAppSecret = async ({ store }) => {
const oauth = useOAuthStore()
if (oauth.userToken) {
store.commit('setBackendInteractor', backendInteractorService(oauth.getToken))
store.commit(
'setBackendInteractor',
backendInteractorService(oauth.getToken),
)
}
}
const resolveStaffAccounts = ({ store, accounts }) => {
const nicknames = accounts.map(uri => uri.split('/').pop())
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
const nicknames = accounts.map((uri) => uri.split('/').pop())
store.dispatch('setInstanceOption', {
name: 'staffAccounts',
value: nicknames,
})
}
const getNodeInfo = async ({ store }) => {
@ -254,76 +262,167 @@ const getNodeInfo = async ({ store }) => {
const data = await res.json()
const metadata = data.metadata
const features = metadata.features
store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName })
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
store.dispatch('setInstanceOption', {
name: 'name',
value: metadata.nodeName,
})
store.dispatch('setInstanceOption', {
name: 'registrationOpen',
value: data.openRegistrations,
})
store.dispatch('setInstanceOption', {
name: 'mediaProxyAvailable',
value: features.includes('media_proxy'),
})
store.dispatch('setInstanceOption', {
name: 'safeDM',
value: features.includes('safe_dm_mentions'),
})
store.dispatch('setInstanceOption', {
name: 'shoutAvailable',
value: features.includes('chat'),
})
store.dispatch('setInstanceOption', {
name: 'pleromaChatMessagesAvailable',
value: features.includes('pleroma_chat_messages'),
})
store.dispatch('setInstanceOption', {
name: 'pleromaCustomEmojiReactionsAvailable',
value:
features.includes('pleroma_custom_emoji_reactions') ||
features.includes('custom_emoji_reactions')
features.includes('custom_emoji_reactions'),
})
store.dispatch('setInstanceOption', {
name: 'pleromaBookmarkFoldersAvailable',
value: features.includes('pleroma:bookmark_folders'),
})
store.dispatch('setInstanceOption', {
name: 'gopherAvailable',
value: features.includes('gopher'),
})
store.dispatch('setInstanceOption', {
name: 'pollsAvailable',
value: features.includes('polls'),
})
store.dispatch('setInstanceOption', {
name: 'editingAvailable',
value: features.includes('editing'),
})
store.dispatch('setInstanceOption', {
name: 'pollLimits',
value: metadata.pollLimits,
})
store.dispatch('setInstanceOption', {
name: 'mailerEnabled',
value: metadata.mailerEnabled,
})
store.dispatch('setInstanceOption', {
name: 'quotingAvailable',
value: features.includes('quote_posting'),
})
store.dispatch('setInstanceOption', {
name: 'groupActorAvailable',
value: features.includes('pleroma:group_actors'),
})
store.dispatch('setInstanceOption', {
name: 'blockExpiration',
value: features.includes('pleroma:block_expiration'),
})
store.dispatch('setInstanceOption', {
name: 'localBubbleInstances',
value: metadata.localBubbleInstances ?? [],
})
store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
store.dispatch('setInstanceOption', {
name: 'uploadlimit',
value: parseInt(uploadLimits.general),
})
store.dispatch('setInstanceOption', {
name: 'avatarlimit',
value: parseInt(uploadLimits.avatar),
})
store.dispatch('setInstanceOption', {
name: 'backgroundlimit',
value: parseInt(uploadLimits.background),
})
store.dispatch('setInstanceOption', {
name: 'bannerlimit',
value: parseInt(uploadLimits.banner),
})
store.dispatch('setInstanceOption', {
name: 'fieldsLimits',
value: metadata.fieldsLimits,
})
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
store.dispatch('setInstanceOption', {
name: 'restrictedNicknames',
value: metadata.restrictedNicknames,
})
store.dispatch('setInstanceOption', {
name: 'postFormats',
value: metadata.postFormats,
})
const suggestions = metadata.suggestions
store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
store.dispatch('setInstanceOption', {
name: 'suggestionsEnabled',
value: suggestions.enabled,
})
store.dispatch('setInstanceOption', {
name: 'suggestionsWeb',
value: suggestions.web,
})
const software = data.software
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
store.dispatch('setInstanceOption', {
name: 'backendVersion',
value: software.version,
})
store.dispatch('setInstanceOption', {
name: 'backendRepository',
value: software.repository,
})
const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv })
const frontendVersion = window.___pleromafe_commit_hash
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
store.dispatch('setInstanceOption', {
name: 'frontendVersion',
value: frontendVersion,
})
const federation = metadata.federation
store.dispatch('setInstanceOption', {
name: 'tagPolicyAvailable',
value: typeof federation.mrf_policies === 'undefined'
? false
: metadata.federation.mrf_policies.includes('TagPolicy')
value:
typeof federation.mrf_policies === 'undefined'
? false
: metadata.federation.mrf_policies.includes('TagPolicy'),
})
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
store.dispatch('setInstanceOption', {
name: 'federationPolicy',
value: federation,
})
store.dispatch('setInstanceOption', {
name: 'federating',
value: typeof federation.enabled === 'undefined'
? true
: federation.enabled
value:
typeof federation.enabled === 'undefined' ? true : federation.enabled,
})
const accountActivationRequired = metadata.accountActivationRequired
store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
store.dispatch('setInstanceOption', {
name: 'accountActivationRequired',
value: accountActivationRequired,
})
const accounts = metadata.staffAccounts
resolveStaffAccounts({ store, accounts })
} else {
throw (res)
throw res
}
} catch (e) {
console.warn('Could not load nodeinfo')
@ -333,7 +432,10 @@ const getNodeInfo = async ({ store }) => {
const setConfig = async ({ store }) => {
// apiConfig, staticConfig
const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
const configInfos = await Promise.all([
getBackendProvidedConfig({ store }),
getStaticConfig(),
])
const apiConfig = configInfos[0]
const staticConfig = configInfos[1]
@ -364,29 +466,37 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
if (process.env.NODE_ENV === 'development') {
// do some checks to avoid common errors
if (!Object.keys(allStores).length) {
throw new Error('No stores are available. Check the code in src/boot/after_store.js')
throw new Error(
'No stores are available. Check the code in src/boot/after_store.js',
)
}
}
await Promise.all(
Object.entries(allStores)
.map(async ([name, mod]) => {
const isStoreName = name => name.startsWith('use')
if (process.env.NODE_ENV === 'development') {
if (Object.keys(mod).filter(isStoreName).length !== 1) {
throw new Error('Each store file must export exactly one store as a named export. Check your code in src/stores/')
}
Object.entries(allStores).map(async ([name, mod]) => {
const isStoreName = (name) => name.startsWith('use')
if (process.env.NODE_ENV === 'development') {
if (Object.keys(mod).filter(isStoreName).length !== 1) {
throw new Error(
'Each store file must export exactly one store as a named export. Check your code in src/stores/',
)
}
const storeFuncName = Object.keys(mod).find(isStoreName)
if (storeFuncName && typeof mod[storeFuncName] === 'function') {
const p = mod[storeFuncName]().$persistLoaded
if (!(p instanceof Promise)) {
throw new Error(`${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`)
}
await p
} else {
throw new Error(`Store module ${name} does not export a 'use...' function`)
}
const storeFuncName = Object.keys(mod).find(isStoreName)
if (storeFuncName && typeof mod[storeFuncName] === 'function') {
const p = mod[storeFuncName]().$persistLoaded
if (!(p instanceof Promise)) {
throw new Error(
`${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`,
)
}
}))
await p
} else {
throw new Error(
`Store module ${name} does not export a 'use...' function`,
)
}
}),
)
}
try {
@ -397,7 +507,10 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
}
if (storageError) {
useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error' })
useInterfaceStore().pushGlobalNotice({
messageKey: 'errors.storage_unavailable',
level: 'error',
})
}
useInterfaceStore().setLayoutWidth(windowWidth())
@ -409,12 +522,19 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
window.addEventListener('focus', () => updateFocus())
const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
const server =
typeof overrides.target !== 'undefined'
? overrides.target
: window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store })
try {
await useInterfaceStore().applyTheme().catch((e) => { console.error('Error setting theme', e) })
await useInterfaceStore()
.applyTheme()
.catch((e) => {
console.error('Error setting theme', e)
})
} catch (e) {
window.splashError(e)
return Promise.reject(e)
@ -428,8 +548,8 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
checkOAuthToken({ store }),
getInstancePanel({ store }),
getNodeInfo({ store }),
getInstanceConfig({ store })
]).catch(e => Promise.reject(e))
getInstanceConfig({ store }),
]).catch((e) => Promise.reject(e))
// Start fetching things that don't need to block the UI
store.dispatch('fetchMutes')
@ -442,11 +562,11 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
history: createWebHistory(),
routes: routes(store),
scrollBehavior: (to, _from, savedPosition) => {
if (to.matched.some(m => m.meta.dontScroll)) {
if (to.matched.some((m) => m.meta.dontScroll)) {
return false
}
return savedPosition || { left: 0, top: 0 }
}
},
})
useI18nStore().setI18n(i18n)

View file

@ -1,35 +1,36 @@
import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
import ConversationPage from 'components/conversation-page/conversation-page.vue'
import Interactions from 'components/interactions/interactions.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue'
import ChatList from 'components/chat_list/chat_list.vue'
import Chat from 'components/chat/chat.vue'
import UserProfile from 'components/user_profile/user_profile.vue'
import Search from 'components/search/search.vue'
import Registration from 'components/registration/registration.vue'
import PasswordReset from 'components/password_reset/password_reset.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import Notifications from 'components/notifications/notifications.vue'
import AuthForm from 'components/auth_form/auth_form.js'
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
import About from 'components/about/about.vue'
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
import Lists from 'components/lists/lists.vue'
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
import ListsEdit from 'components/lists_edit/lists_edit.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
import AuthForm from 'components/auth_form/auth_form.js'
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import Chat from 'components/chat/chat.vue'
import ChatList from 'components/chat_list/chat_list.vue'
import ConversationPage from 'components/conversation-page/conversation-page.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue'
import Drafts from 'components/drafts/drafts.vue'
import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import Interactions from 'components/interactions/interactions.vue'
import Lists from 'components/lists/lists.vue'
import ListsEdit from 'components/lists_edit/lists_edit.vue'
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
import Notifications from 'components/notifications/notifications.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import PasswordReset from 'components/password_reset/password_reset.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import Registration from 'components/registration/registration.vue'
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
import Search from 'components/search/search.vue'
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
import UserProfile from 'components/user_profile/user_profile.vue'
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue'
import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@ -45,46 +46,124 @@ export default (store) => {
name: 'root',
path: '/',
redirect: () => {
return (store.state.users.currentUser
? store.state.instance.redirectRootLogin
: store.state.instance.redirectRootNoLogin) || '/main/all'
}
return (
(store.state.users.currentUser
? store.state.instance.redirectRootLogin
: store.state.instance.redirectRootNoLogin) || '/main/all'
)
},
},
{
name: 'public-external-timeline',
path: '/main/all',
component: PublicAndExternalTimeline,
},
{
name: 'public-timeline',
path: '/main/public',
component: PublicTimeline,
},
{
name: 'friends',
path: '/main/friends',
component: FriendsTimeline,
beforeEnter: validateAuthenticatedRoute,
},
{ name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline },
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{
name: 'conversation',
path: '/notice/:id',
component: ConversationPage,
meta: { dontScroll: true },
},
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{
name: 'remote-user-profile-acct',
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'remote-user-profile',
path: '/remote-users/:hostname/:username',
component: RemoteUserResolver,
beforeEnter: validateAuthenticatedRoute
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'external-user-profile',
path: '/users/$:id',
component: UserProfile,
},
{
name: 'interactions',
path: '/users/:username/interactions',
component: Interactions,
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'dms',
path: '/users/:username/dms',
component: DMs,
beforeEnter: validateAuthenticatedRoute,
},
{ name: 'external-user-profile', path: '/users/$:id', component: UserProfile },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
{ name: 'registration-token', path: '/registration/:token', component: Registration },
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute },
{
name: 'password-reset',
path: '/password-reset',
component: PasswordReset,
props: true,
},
{
name: 'registration-token',
path: '/registration/:token',
component: Registration,
},
{
name: 'friend-requests',
path: '/friend-requests',
component: FollowRequests,
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'notifications',
path: '/:username/notifications',
component: Notifications,
props: () => ({ disableTeleport: true }),
beforeEnter: validateAuthenticatedRoute,
},
{ name: 'login', path: '/login', component: AuthForm },
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
{ 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: 'shout-panel',
path: '/shout-panel',
component: ShoutPanel,
props: () => ({ floating: false }),
},
{
name: 'oauth-callback',
path: '/oauth-callback',
component: OAuthCallback,
props: (route) => ({ code: route.query.code }),
},
{
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: 'about', path: '/about', component: About },
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage },
{
name: 'announcements',
path: '/announcements',
component: AnnouncementsPage,
},
{ name: 'drafts', path: '/drafts', component: Drafts },
{ name: 'user-profile', path: '/users/:name', component: UserProfile },
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile },
@ -92,17 +171,51 @@ export default (store) => {
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
{ name: 'lists-new', path: '/lists/new', component: ListsEdit },
{ name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute },
{ name: 'bookmark-folders', path: '/bookmark_folders', component: BookmarkFolders },
{ name: 'bookmark-folder-new', path: '/bookmarks/new-folder', component: BookmarkFolderEdit },
{ name: 'bookmark-folder', path: '/bookmarks/:id', component: BookmarkTimeline },
{ name: 'bookmark-folder-edit', path: '/bookmarks/:id/edit', component: BookmarkFolderEdit }
{
name: 'edit-navigation',
path: '/nav-edit',
component: NavPanel,
props: () => ({ forceExpand: true, forceEditMode: true }),
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'bookmark-folders',
path: '/bookmark_folders',
component: BookmarkFolders,
},
{
name: 'bookmark-folder-new',
path: '/bookmarks/new-folder',
component: BookmarkFolderEdit,
},
{
name: 'bookmark-folder',
path: '/bookmarks/:id',
component: BookmarkTimeline,
},
{
name: 'bookmark-folder-edit',
path: '/bookmarks/:id/edit',
component: BookmarkFolderEdit,
},
]
if (store.state.instance.pleromaChatMessagesAvailable) {
routes = routes.concat([
{ name: 'chat', path: '/users/:username/chats/:recipient_id', component: Chat, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute },
{ name: 'chats', path: '/users/:username/chats', component: ChatList, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute }
{
name: 'chat',
path: '/users/:username/chats/:recipient_id',
component: Chat,
meta: { dontScroll: false },
beforeEnter: validateAuthenticatedRoute,
},
{
name: 'chats',
path: '/users/:username/chats',
component: ChatList,
meta: { dontScroll: false },
beforeEnter: validateAuthenticatedRoute,
},
])
}

View file

@ -1,8 +1,8 @@
import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from '../features_panel/features_panel.vue'
import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
import StaffPanel from '../staff_panel/staff_panel.vue'
import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
import StaffPanel from '../staff_panel/staff_panel.vue'
import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
const About = {
components: {
@ -10,16 +10,20 @@ const About = {
FeaturesPanel,
TermsOfServicePanel,
StaffPanel,
MRFTransparencyPanel
MRFTransparencyPanel,
},
computed: {
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
showFeaturesPanel() {
return this.$store.state.instance.showFeaturesPanel
},
showInstanceSpecificPanel() {
return (
this.$store.state.instance.showInstanceSpecificPanel &&
!this.$store.getters.mergedConfig.hideISP &&
this.$store.state.instance.instanceSpecificPanelContent
}
}
)
},
},
}
export default About

View file

@ -1,99 +1,103 @@
import { mapState } from 'vuex'
import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisV
} from '@fortawesome/free-solid-svg-icons'
import { useReportsStore } from 'src/stores/reports'
library.add(
faEllipsisV
)
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
import { useReportsStore } from 'src/stores/reports'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import Popover from '../popover/popover.vue'
import ProgressButton from '../progress_button/progress_button.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
library.add(faEllipsisV)
const AccountActions = {
props: [
'user', 'relationship'
],
data () {
props: ['user', 'relationship'],
data() {
return {
showingConfirmBlock: false,
showingConfirmRemoveFollower: false
showingConfirmRemoveFollower: false,
}
},
components: {
ProgressButton,
Popover,
UserListMenu,
ConfirmModal
ConfirmModal,
UserTimedFilterModal,
},
methods: {
showConfirmBlock () {
this.showingConfirmBlock = true
},
hideConfirmBlock () {
this.showingConfirmBlock = false
},
showConfirmRemoveUserFromFollowers () {
showConfirmRemoveUserFromFollowers() {
this.showingConfirmRemoveFollower = true
},
hideConfirmRemoveUserFromFollowers () {
hideConfirmRemoveUserFromFollowers() {
this.showingConfirmRemoveFollower = false
},
showRepeats () {
hideConfirmBlock() {
this.showingConfirmBlock = false
},
showRepeats() {
this.$store.dispatch('showReblogs', this.user.id)
},
hideRepeats () {
hideRepeats() {
this.$store.dispatch('hideReblogs', this.user.id)
},
blockUser () {
if (!this.shouldConfirmBlock) {
this.doBlockUser()
blockUser() {
if (this.$refs.timedBlockDialog) {
this.$refs.timedBlockDialog.optionallyPrompt()
} else {
this.showConfirmBlock()
if (!this.shouldConfirmBlock) {
this.doBlockUser()
} else {
this.showingConfirmBlock = true
}
}
},
doBlockUser () {
this.$store.dispatch('blockUser', this.user.id)
doBlockUser() {
this.$store.dispatch('blockUser', { id: this.user.id })
this.hideConfirmBlock()
},
unblockUser () {
unblockUser() {
this.$store.dispatch('unblockUser', this.user.id)
},
removeUserFromFollowers () {
removeUserFromFollowers() {
if (!this.shouldConfirmRemoveUserFromFollowers) {
this.doRemoveUserFromFollowers()
} else {
this.showConfirmRemoveUserFromFollowers()
}
},
doRemoveUserFromFollowers () {
doRemoveUserFromFollowers() {
this.$store.dispatch('removeUserFromFollowers', this.user.id)
this.hideConfirmRemoveUserFromFollowers()
},
reportUser () {
reportUser() {
useReportsStore().openUserReportingModal({ userId: this.user.id })
},
openChat () {
openChat() {
this.$router.push({
name: 'chat',
params: { username: this.$store.state.users.currentUser.screen_name, recipient_id: this.user.id }
params: {
username: this.$store.state.users.currentUser.screen_name,
recipient_id: this.user.id,
},
})
}
},
},
computed: {
shouldConfirmBlock () {
shouldConfirmBlock() {
return this.$store.getters.mergedConfig.modalOnBlock
},
shouldConfirmRemoveUserFromFollowers () {
shouldConfirmRemoveUserFromFollowers() {
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
},
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
})
}
blockExpirationSupported: (state) => state.instance.blockExpiration,
pleromaChatMessagesAvailable: (state) =>
state.instance.pleromaChatMessagesAvailable,
}),
},
}
export default AccountActions

View file

@ -3,7 +3,6 @@
<Popover
trigger="click"
placement="bottom"
:bound-to="{ x: 'container' }"
remove-padding
>
<template #content>
@ -96,7 +95,8 @@
</Popover>
<teleport to="#modal">
<confirm-modal
v-if="showingConfirmBlock"
v-if="showingConfirmBlock && !blockExpirationSupported"
ref="blockDialog"
:title="$t('user_card.block_confirm_title')"
:confirm-text="$t('user_card.block_confirm_accept_button')"
:cancel-text="$t('user_card.block_confirm_cancel_button')"
@ -137,6 +137,12 @@
</template>
</i18n-t>
</confirm-modal>
<UserTimedFilterModal
v-if="blockExpirationSupported"
ref="timedBlockDialog"
:is-mute="false"
:user="user"
/>
</teleport>
</div>
</template>

View file

@ -1,57 +1,51 @@
export default {
name: 'Alert',
selector: '.alert',
validInnerComponents: [
'Text',
'Icon',
'Link',
'Border',
'ButtonUnstyled'
],
validInnerComponents: ['Text', 'Icon', 'Link', 'Border', 'ButtonUnstyled'],
variants: {
normal: '.neutral',
error: '.error',
warning: '.warning',
success: '.success'
success: '.success',
},
editor: {
border: 1,
aspect: '3 / 1'
aspect: '3 / 1',
},
defaultRules: [
{
directives: {
background: '--text',
opacity: 0.5,
blur: '9px'
}
blur: '9px',
},
},
{
parent: {
component: 'Alert'
component: 'Alert',
},
component: 'Border',
directives: {
textColor: '--parent'
}
textColor: '--parent',
},
},
{
variant: 'error',
directives: {
background: '--cRed'
}
background: '--cRed',
},
},
{
variant: 'warning',
directives: {
background: '--cOrange'
}
background: '--cOrange',
},
},
{
variant: 'success',
directives: {
background: '--cGreen'
}
}
]
background: '--cGreen',
},
},
],
}

View file

@ -1,109 +1,129 @@
import { mapState } from 'vuex'
import { useAnnouncementsStore } from 'src/stores/announcements'
import localeService from '../../services/locale/locale.service.js'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
import RichContent from '../rich_content/rich_content.jsx'
import localeService from '../../services/locale/locale.service.js'
import { useAnnouncementsStore } from 'src/stores/announcements'
const Announcement = {
components: {
AnnouncementEditor,
RichContent
RichContent,
},
data () {
data() {
return {
editing: false,
editedAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
allDay: undefined
allDay: undefined,
},
editError: ''
editError: '',
}
},
props: {
announcement: Object
announcement: Object,
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
currentUser: (state) => state.users.currentUser,
}),
canEditAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
canEditAnnouncement() {
return (
this.currentUser &&
this.currentUser.privileges.includes(
'announcements_manage_announcements',
)
)
},
content () {
content() {
return this.announcement.content
},
isRead () {
isRead() {
return this.announcement.read
},
publishedAt () {
publishedAt() {
const time = this.announcement.published_at
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
return this.formatTimeOrDate(
time,
localeService.internalToBrowserLocale(this.$i18n.locale),
)
},
startsAt () {
startsAt() {
const time = this.announcement.starts_at
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
return this.formatTimeOrDate(
time,
localeService.internalToBrowserLocale(this.$i18n.locale),
)
},
endsAt () {
endsAt() {
const time = this.announcement.ends_at
if (!time) {
return
}
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
return this.formatTimeOrDate(
time,
localeService.internalToBrowserLocale(this.$i18n.locale),
)
},
inactive () {
inactive() {
return this.announcement.inactive
}
},
},
methods: {
markAsRead () {
markAsRead() {
if (!this.isRead) {
return useAnnouncementsStore().markAnnouncementAsRead(this.announcement.id)
return useAnnouncementsStore().markAnnouncementAsRead(
this.announcement.id,
)
}
},
deleteAnnouncement () {
deleteAnnouncement() {
return useAnnouncementsStore().deleteAnnouncement(this.announcement.id)
},
formatTimeOrDate (time, locale) {
formatTimeOrDate(time, locale) {
const d = new Date(time)
return this.announcement.all_day ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
return this.announcement.all_day
? d.toLocaleDateString(locale)
: d.toLocaleString(locale)
},
enterEditMode () {
enterEditMode() {
this.editedAnnouncement.content = this.announcement.pleroma.raw_content
this.editedAnnouncement.startsAt = this.announcement.starts_at
this.editedAnnouncement.endsAt = this.announcement.ends_at
this.editedAnnouncement.allDay = this.announcement.all_day
this.editing = true
},
submitEdit () {
useAnnouncementsStore().editAnnouncement({
id: this.announcement.id,
...this.editedAnnouncement
})
submitEdit() {
useAnnouncementsStore()
.editAnnouncement({
id: this.announcement.id,
...this.editedAnnouncement,
})
.then(() => {
this.editing = false
})
.catch(error => {
.catch((error) => {
this.editError = error.error
})
},
cancelEdit () {
cancelEdit() {
this.editing = false
},
clearError () {
clearError() {
this.editError = undefined
}
}
},
},
}
export default Announcement

View file

@ -2,12 +2,12 @@ import Checkbox from '../checkbox/checkbox.vue'
const AnnouncementEditor = {
components: {
Checkbox
Checkbox,
},
props: {
announcement: Object,
disabled: Boolean
}
disabled: Boolean,
},
}
export default AnnouncementEditor

View file

@ -1,59 +1,66 @@
import { mapState } from 'vuex'
import { useAnnouncementsStore } from 'src/stores/announcements'
import Announcement from '../announcement/announcement.vue'
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
import { useAnnouncementsStore } from 'src/stores/announcements'
const AnnouncementsPage = {
components: {
Announcement,
AnnouncementEditor
AnnouncementEditor,
},
data () {
data() {
return {
newAnnouncement: {
content: '',
startsAt: undefined,
endsAt: undefined,
allDay: false
allDay: false,
},
posting: false,
error: undefined
error: undefined,
}
},
mounted () {
mounted() {
useAnnouncementsStore().fetchAnnouncements()
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
currentUser: (state) => state.users.currentUser,
}),
announcements () {
announcements() {
return useAnnouncementsStore().announcements
},
canPostAnnouncement () {
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
}
canPostAnnouncement() {
return (
this.currentUser &&
this.currentUser.privileges.includes(
'announcements_manage_announcements',
)
)
},
},
methods: {
postAnnouncement () {
postAnnouncement() {
this.posting = true
useAnnouncementsStore().postAnnouncement(this.newAnnouncement)
useAnnouncementsStore()
.postAnnouncement(this.newAnnouncement)
.then(() => {
this.newAnnouncement.content = ''
this.startsAt = undefined
this.endsAt = undefined
})
.catch(error => {
.catch((error) => {
this.error = error.error
})
.finally(() => {
this.posting = false
})
},
clearError () {
clearError() {
this.error = undefined
}
}
},
},
}
export default AnnouncementsPage

View file

@ -21,10 +21,10 @@
export default {
emits: ['resetAsyncComponent'],
methods: {
retry () {
retry() {
this.$emit('resetAsyncComponent')
}
}
},
},
}
</script>

View file

@ -1,24 +1,26 @@
import StillImage from '../still-image/still-image.vue'
import Flash from '../flash/flash.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import { mapGetters } from 'vuex'
import { useMediaViewerStore } from 'src/stores/media_viewer'
import nsfwImage from '../../assets/nsfw.png'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { mapGetters } from 'vuex'
import Flash from '../flash/flash.vue'
import StillImage from '../still-image/still-image.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faAlignRight,
faFile,
faMusic,
faImage,
faVideo,
faPlayCircle,
faTimes,
faStop,
faSearchPlus,
faTrashAlt,
faMusic,
faPencilAlt,
faAlignRight
faPlayCircle,
faSearchPlus,
faStop,
faTimes,
faTrashAlt,
faVideo,
} from '@fortawesome/free-solid-svg-icons'
import { useMediaViewerStore } from 'src/stores/media_viewer'
library.add(
faFile,
@ -31,7 +33,7 @@ library.add(
faSearchPlus,
faTrashAlt,
faPencilAlt,
faAlignRight
faAlignRight,
)
const Attachment = {
@ -46,72 +48,74 @@ const Attachment = {
'remove',
'shiftUp',
'shiftDn',
'edit'
'edit',
],
data () {
data() {
return {
localDescription: this.description || this.attachment.description,
nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
preloadImage: this.$store.getters.mergedConfig.preloadImage,
loading: false,
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
img:
fileTypeService.fileType(this.attachment.mimetype) === 'image' &&
document.createElement('img'),
modalOpen: false,
showHidden: false,
flashLoaded: false,
showDescription: false
showDescription: false,
}
},
components: {
Flash,
StillImage,
VideoAttachment
VideoAttachment,
},
computed: {
classNames () {
classNames() {
return [
{
'-loading': this.loading,
'-nsfw-placeholder': this.hidden,
'-editable': this.edit !== undefined,
'-compact': this.compact
'-compact': this.compact,
},
'-type-' + this.type,
this.size && '-size-' + this.size,
`-${this.useContainFit ? 'contain' : 'cover'}-fit`
`-${this.useContainFit ? 'contain' : 'cover'}-fit`,
]
},
usePlaceholder () {
usePlaceholder() {
return this.size === 'hide'
},
useContainFit () {
useContainFit() {
return this.$store.getters.mergedConfig.useContainFit
},
placeholderName () {
placeholderName() {
if (this.attachment.description === '' || !this.attachment.description) {
return this.type.toUpperCase()
}
return this.attachment.description
},
placeholderIconClass () {
placeholderIconClass() {
if (this.type === 'image') return 'image'
if (this.type === 'video') return 'video'
if (this.type === 'audio') return 'music'
return 'file'
},
referrerpolicy () {
referrerpolicy() {
return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
},
type () {
type() {
return fileTypeService.fileType(this.attachment.mimetype)
},
hidden () {
hidden() {
return this.nsfw && this.hideNsfwLocal && !this.showHidden
},
isEmpty () {
return (this.type === 'html' && !this.attachment.oembed)
isEmpty() {
return this.type === 'html' && !this.attachment.oembed
},
useModal () {
useModal() {
let modalTypes = []
switch (this.size) {
case 'hide':
@ -126,26 +130,26 @@ const Attachment = {
}
return modalTypes.includes(this.type)
},
videoTag () {
videoTag() {
return this.useModal ? 'button' : 'span'
},
...mapGetters(['mergedConfig'])
...mapGetters(['mergedConfig']),
},
watch: {
'attachment.description' (newVal) {
'attachment.description'(newVal) {
this.localDescription = newVal
},
localDescription (newVal) {
localDescription(newVal) {
this.onEdit(newVal)
}
},
},
methods: {
linkClicked ({ target }) {
linkClicked({ target }) {
if (target.tagName === 'A') {
window.open(target.href, '_blank')
}
},
openModal () {
openModal() {
if (this.useModal) {
this.$emit('setMedia')
useMediaViewerStore().setCurrentMedia(this.attachment)
@ -153,34 +157,35 @@ const Attachment = {
window.open(this.attachment.url)
}
},
openModalForce () {
openModalForce() {
this.$emit('setMedia')
useMediaViewerStore().setCurrentMedia(this.attachment)
},
onEdit (event) {
onEdit(event) {
this.edit && this.edit(this.attachment, event)
},
onRemove () {
onRemove() {
this.remove && this.remove(this.attachment)
},
onShiftUp () {
onShiftUp() {
this.shiftUp && this.shiftUp(this.attachment)
},
onShiftDn () {
onShiftDn() {
this.shiftDn && this.shiftDn(this.attachment)
},
stopFlash () {
stopFlash() {
this.$refs.flash.closePlayer()
},
setFlashLoaded (event) {
setFlashLoaded(event) {
this.flashLoaded = event
},
toggleDescription () {
toggleDescription() {
this.showDescription = !this.showDescription
},
toggleHidden (event) {
toggleHidden(event) {
if (
(this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
this.mergedConfig.useOneClickNsfw &&
!this.showHidden &&
(this.type !== 'video' || this.mergedConfig.playVideosInModal)
) {
this.openModal(event)
@ -201,12 +206,12 @@ const Attachment = {
this.showHidden = !this.showHidden
}
},
onImageLoad (image) {
onImageLoad(image) {
const width = image.naturalWidth
const height = image.naturalHeight
this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
}
}
},
},
}
export default Attachment

View file

@ -107,9 +107,9 @@
.play-icon {
position: absolute;
font-size: 64px;
top: calc(50% - 32px);
left: calc(50% - 32px);
font-size: 4.5em;
top: calc(50% - 2.25rem);
left: calc(50% - 2.25rem);
color: rgb(255 255 255 / 75%);
text-shadow: 0 0 2px rgb(0 0 0 / 40%);

View file

@ -1,27 +0,0 @@
export default {
name: 'Attachment',
selector: '.Attachment',
notEditable: true,
validInnerComponents: [
'Border',
'Button',
'Input'
],
defaultRules: [
{
directives: {
roundness: 3
}
},
{
component: 'Button',
parent: {
component: 'Attachment'
},
directives: {
background: '#FFFFFF',
opacity: 0.5
}
}
]
}

View file

@ -23,7 +23,7 @@
>
<button
v-if="remove"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
@click.prevent="onRemove"
>
<FAIcon icon="trash-alt" />
@ -81,7 +81,7 @@
>
<button
v-if="type === 'flash' && flashLoaded"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.attachment_stop_flash')"
@click.prevent="stopFlash"
>
@ -89,7 +89,7 @@
</button>
<button
v-if="attachment.description && size !== 'small' && !edit && type !== 'unknown'"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.show_attachment_description')"
@click.prevent="toggleDescription"
>
@ -97,7 +97,7 @@
</button>
<button
v-if="!useModal && type !== 'unknown'"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.show_attachment_in_modal')"
@click.prevent="openModalForce"
>
@ -105,7 +105,7 @@
</button>
<button
v-if="nsfw && hideNsfwLocal"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.hide_attachment')"
@click.prevent="toggleHidden"
>
@ -113,7 +113,7 @@
</button>
<button
v-if="shiftUp"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.move_up')"
@click.prevent="onShiftUp"
>
@ -121,7 +121,7 @@
</button>
<button
v-if="shiftDn"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.move_down')"
@click.prevent="onShiftDn"
>
@ -129,7 +129,7 @@
</button>
<button
v-if="remove"
class="button-default attachment-button"
class="button-default attachment-button -transparent"
:title="$t('status.remove_attachment')"
@click.prevent="onRemove"
>

View file

@ -1,28 +1,33 @@
import { mapState } from 'pinia'
import { h, resolveComponent } from 'vue'
import { useAuthFlowStore } from 'src/stores/auth_flow'
import LoginForm from '../login_form/login_form.vue'
import MFARecoveryForm from '../mfa_form/recovery_form.vue'
import MFATOTPForm from '../mfa_form/totp_form.vue'
import { mapState } from 'pinia'
import { useAuthFlowStore } from 'src/stores/auth_flow'
const AuthForm = {
name: 'AuthForm',
render () {
render() {
return h(resolveComponent(this.authForm))
},
computed: {
authForm () {
if (this.requiredTOTP) { return 'MFATOTPForm' }
if (this.requiredRecovery) { return 'MFARecoveryForm' }
authForm() {
if (this.requiredTOTP) {
return 'MFATOTPForm'
}
if (this.requiredRecovery) {
return 'MFARecoveryForm'
}
return 'LoginForm'
},
...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery'])
...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery']),
},
components: {
MFARecoveryForm,
MFATOTPForm,
LoginForm
}
LoginForm,
},
}
export default AuthForm

View file

@ -2,51 +2,55 @@ const debounceMilliseconds = 500
export default {
props: {
query: { // function to query results and return a promise
query: {
// function to query results and return a promise
type: Function,
required: true
required: true,
},
filter: { // function to filter results in real time
type: Function
filter: {
// function to filter results in real time
type: Function,
},
placeholder: {
type: String,
default: 'Search...'
}
default: 'Search...',
},
},
data () {
data() {
return {
term: '',
timeout: null,
results: [],
resultsVisible: false
resultsVisible: false,
}
},
computed: {
filtered () {
filtered() {
return this.filter ? this.filter(this.results) : this.results
}
},
},
watch: {
term (val) {
term(val) {
this.fetchResults(val)
}
},
},
methods: {
fetchResults (term) {
fetchResults(term) {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
this.results = []
if (term) {
this.query(term).then((results) => { this.results = results })
this.query(term).then((results) => {
this.results = results
})
}
}, debounceMilliseconds)
},
onInputClick () {
onInputClick() {
this.resultsVisible = true
},
onClickOutside () {
onClickOutside() {
this.resultsVisible = false
}
}
},
},
}

View file

@ -1,21 +1,25 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import UserAvatar from '../user_avatar/user_avatar.vue'
const AvatarList = {
props: ['users'],
computed: {
slicedUsers () {
slicedUsers() {
return this.users ? this.users.slice(0, 15) : []
}
},
},
components: {
UserAvatar
UserAvatar,
},
methods: {
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}
}
userProfileLink(user) {
return generateProfileLink(
user.id,
user.screen_name,
this.$store.state.instance.restrictedNicknames,
)
},
},
}
export default AvatarList

View file

@ -1,30 +1,27 @@
export default {
name: 'Badge',
selector: '.badge',
validInnerComponents: [
'Text',
'Icon'
],
validInnerComponents: ['Text', 'Icon'],
variants: {
notification: '.-notification'
notification: '.-notification',
},
defaultRules: [
{
component: 'Root',
directives: {
'--badgeNotification': 'color | --cRed'
}
'--badgeNotification': 'color | --cRed',
},
},
{
directives: {
background: '--cGreen'
}
background: '--cGreen',
},
},
{
variant: 'notification',
directives: {
background: '--cRed'
}
}
]
background: '--cRed',
},
},
],
}

View file

@ -1,24 +1,26 @@
import UserPopover from '../user_popover/user_popover.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserLink from '../user_link/user_link.vue'
import UserPopover from '../user_popover/user_popover.vue'
const BasicUserCard = {
props: [
'user'
],
props: ['user'],
components: {
UserPopover,
UserAvatar,
RichContent,
UserLink
UserLink,
},
methods: {
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}
}
userProfileLink(user) {
return generateProfileLink(
user.id,
user.screen_name,
this.$store.state.instance.restrictedNicknames,
)
},
},
}
export default BasicUserCard

View file

@ -1,40 +1,48 @@
import { mapState } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
const BlockCard = {
props: ['userId'],
data () {
return {
progress: false
}
},
computed: {
user () {
user() {
return this.$store.getters.findUser(this.userId)
},
relationship () {
relationship() {
return this.$store.getters.relationship(this.userId)
},
blocked () {
blocked() {
return this.relationship.blocking
}
},
blockExpiryAvailable() {
return Object.hasOwn(this.user, 'block_expires_at')
},
blockExpiry() {
return this.user.block_expires_at === false
? this.$t('user_card.block_expires_forever')
: this.$t('user_card.block_expires_at', [
new Date(this.user.mute_expires_at).toLocaleString(),
])
},
...mapState({
blockExpirationSupported: (state) => state.instance.blockExpiration,
}),
},
components: {
BasicUserCard
BasicUserCard,
},
methods: {
unblockUser () {
this.progress = true
this.$store.dispatch('unblockUser', this.user.id).then(() => {
this.progress = false
})
unblockUser() {
this.$store.dispatch('unblockUser', this.user.id)
},
blockUser () {
this.progress = true
this.$store.dispatch('blockUser', this.user.id).then(() => {
this.progress = false
})
}
}
blockUser() {
if (this.blockExpirationSupported) {
this.$refs.timedBlockDialog.optionallyPrompt()
} else {
this.$store.dispatch('blockUser', { id: this.user.id })
}
},
},
}
export default BlockCard

View file

@ -1,33 +1,35 @@
<template>
<basic-user-card :user="user">
<div class="block-card-content-container">
<span
v-if="blocked && blockExpiryAvailable"
class="alert neutral"
>
{{ blockExpiry }}
</span>
{{ ' ' }}
<button
v-if="blocked"
class="btn button-default"
:disabled="progress"
@click="unblockUser"
>
<template v-if="progress">
{{ $t('user_card.unblock_progress') }}
</template>
<template v-else>
{{ $t('user_card.unblock') }}
</template>
{{ $t('user_card.unblock') }}
</button>
<button
v-else
class="btn button-default"
:disabled="progress"
@click="blockUser"
>
<template v-if="progress">
{{ $t('user_card.block_progress') }}
</template>
<template v-else>
{{ $t('user_card.block') }}
</template>
{{ $t('user_card.block') }}
</button>
</div>
<teleport to="#modal">
<UserTimedFilterModal
ref="timedBlockDialog"
:user="user"
:is-mute="false"
/>
</teleport>
</basic-user-card>
</template>

View file

@ -1,22 +1,15 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisH
} from '@fortawesome/free-solid-svg-icons'
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
library.add(
faEllipsisH
)
library.add(faEllipsisH)
const BookmarkFolderCard = {
props: [
'folder',
'allBookmarks'
],
props: ['folder', 'allBookmarks'],
computed: {
firstLetter () {
firstLetter() {
return this.folder ? this.folder.name[0] : null
}
}
},
},
}
export default BookmarkFolderCard

View file

@ -1,10 +1,10 @@
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
import apiService from '../../services/api/api.service'
import { useInterfaceStore } from 'src/stores/interface'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
import { useInterfaceStore } from 'src/stores/interface'
import apiService from '../../services/api/api.service'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
const BookmarkFolderEdit = {
data () {
data() {
return {
name: '',
nameDraft: '',
@ -13,54 +13,59 @@ const BookmarkFolderEdit = {
emojiDraft: '',
emojiUrlDraft: null,
emojiPickerExpanded: false,
reallyDelete: false
reallyDelete: false,
}
},
components: {
EmojiPicker
EmojiPicker,
},
created () {
created() {
if (!this.id) return
const credentials = this.$store.state.users.currentUser.credentials
apiService.fetchBookmarkFolders({ credentials })
.then((folders) => {
const folder = folders.find(folder => folder.id === this.id)
if (!folder) return
apiService.fetchBookmarkFolders({ credentials }).then((folders) => {
const folder = folders.find((folder) => folder.id === this.id)
if (!folder) return
this.nameDraft = this.name = folder.name
this.emojiDraft = this.emoji = folder.emoji
this.emojiUrlDraft = this.emojiUrl = folder.emoji_url
})
this.nameDraft = this.name = folder.name
this.emojiDraft = this.emoji = folder.emoji
this.emojiUrlDraft = this.emojiUrl = folder.emoji_url
})
},
computed: {
id () {
id() {
return this.$route.params.id
}
},
},
methods: {
selectEmoji (event) {
selectEmoji(event) {
this.emojiDraft = event.insertion
this.emojiUrlDraft = event.insertionUrl
},
showEmojiPicker () {
showEmojiPicker() {
if (!this.emojiPickerExpanded) {
this.$refs.picker.showPicker()
}
},
onShowPicker () {
onShowPicker() {
this.emojiPickerExpanded = true
},
onClosePicker () {
onClosePicker() {
this.emojiPickerExpanded = false
},
updateFolder () {
useBookmarkFoldersStore().updateBookmarkFolder({ folderId: this.id, name: this.nameDraft, emoji: this.emojiDraft })
updateFolder() {
useBookmarkFoldersStore()
.updateBookmarkFolder({
folderId: this.id,
name: this.nameDraft,
emoji: this.emojiDraft,
})
.then(() => {
this.$router.push({ name: 'bookmark-folders' })
})
},
createFolder () {
useBookmarkFoldersStore().createBookmarkFolder({ name: this.nameDraft, emoji: this.emojiDraft })
createFolder() {
useBookmarkFoldersStore()
.createBookmarkFolder({ name: this.nameDraft, emoji: this.emojiDraft })
.then(() => {
this.$router.push({ name: 'bookmark-folders' })
})
@ -68,15 +73,15 @@ const BookmarkFolderEdit = {
useInterfaceStore().pushGlobalNotice({
messageKey: 'bookmark_folders.error',
messageArgs: [e.message],
level: 'error'
level: 'error',
})
})
},
deleteFolder () {
deleteFolder() {
useBookmarkFoldersStore().deleteBookmarkFolder({ folderId: this.id })
this.$router.push({ name: 'bookmark-folders' })
}
}
},
},
}
export default BookmarkFolderEdit

View file

@ -1,28 +1,28 @@
import BookmarkFolderCard from '../bookmark_folder_card/bookmark_folder_card.vue'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
import BookmarkFolderCard from '../bookmark_folder_card/bookmark_folder_card.vue'
const BookmarkFolders = {
data () {
data() {
return {
isNew: false
isNew: false,
}
},
components: {
BookmarkFolderCard
BookmarkFolderCard,
},
computed: {
bookmarkFolders () {
bookmarkFolders() {
return useBookmarkFoldersStore().allFolders
}
},
},
methods: {
cancelNewFolder () {
cancelNewFolder() {
this.isNew = false
},
newFolder () {
newFolder() {
this.isNew = true
}
}
},
},
}
export default BookmarkFolders

View file

@ -1,20 +1,19 @@
import { mapState } from 'pinia'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { getBookmarkFolderEntries } from 'src/components/navigation/filter.js'
import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders'
export const BookmarkFoldersMenuContent = {
props: [
'showPin'
],
props: ['showPin'],
components: {
NavigationEntry
NavigationEntry,
},
computed: {
...mapState(useBookmarkFoldersStore, {
folders: getBookmarkFolderEntries
})
}
folders: getBookmarkFolderEntries,
}),
},
}
export default BookmarkFoldersMenuContent

View file

@ -1,32 +1,38 @@
import Timeline from '../timeline/timeline.vue'
const Bookmarks = {
created () {
created() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'bookmarks',
bookmarkFolderId: this.folderId || null,
})
},
components: {
Timeline
Timeline,
},
computed: {
folderId () {
folderId() {
return this.$route.params.id
},
timeline () {
timeline() {
return this.$store.state.statuses.timelines.bookmarks
}
},
},
watch: {
folderId () {
folderId() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
this.$store.dispatch('startFetchingTimeline', { timeline: 'bookmarks', bookmarkFolderId: this.folderId || null })
}
this.$store.dispatch('startFetchingTimeline', {
timeline: 'bookmarks',
bookmarkFolderId: this.folderId || null,
})
},
},
unmounted () {
unmounted() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
}
},
}
export default Bookmarks

View file

@ -6,8 +6,8 @@ export default {
{
directives: {
textColor: '$mod(--parent 10)',
textAuto: 'no-auto'
}
}
]
textAuto: 'no-auto',
},
},
],
}

View file

@ -1,18 +1,20 @@
import Timeline from '../timeline/timeline.vue'
const BubbleTimeline = {
components: {
Timeline
Timeline,
},
computed: {
timeline () { return this.$store.state.statuses.timelines.bubble }
timeline() {
return this.$store.state.statuses.timelines.bubble
},
},
created () {
created() {
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
},
unmounted () {
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'bubble')
}
},
}
export default BubbleTimeline

View file

@ -10,26 +10,24 @@ export default {
// normal: '' // normal state is implicitly added, it is always included
toggled: '.toggled',
focused: ':focus-within',
pressed: ':focus:active',
pressed: ':active',
hover: ':is(:hover, :focus-visible):not(:disabled)',
disabled: ':disabled'
disabled: ':disabled',
},
// Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it.
variants: {
// Variants save on computation time since adding new variant just adds one more "set".
// normal: '', // you can override normal variant, it will be appenended to the main class
danger: '.danger'
danger: '.-danger',
transparent: '.-transparent',
// Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants.
// This (currently) is further multipled by number of places where component can exist.
},
editor: {
aspect: '2 / 1'
aspect: '2 / 1',
},
// This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever).
validInnerComponents: [
'Text',
'Icon'
],
validInnerComponents: ['Text', 'Icon'],
// Default rules, used as "default theme", essentially.
defaultRules: [
{
@ -38,9 +36,11 @@ export default {
'--buttonDefaultHoverGlow': 'shadow | 0 0 1 2 --text / 0.4',
'--buttonDefaultFocusGlow': 'shadow | 0 0 1 2 --link / 0.5',
'--buttonDefaultShadow': 'shadow | 0 0 2 #000000',
'--buttonDefaultBevel': 'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)',
'--buttonPressedBevel': 'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)'
}
'--buttonDefaultBevel':
'shadow | $borderSide(#FFFFFF top 0.2 1), $borderSide(#000000 bottom 0.2 1)',
'--buttonPressedBevel':
'shadow | inset 0 0 4 #000000, $borderSide(#FFFFFF bottom 0.2 1), $borderSide(#000000 top 0.2 1)',
},
},
{
// component: 'Button', // no need to specify components every time unless you're specifying how other component should look
@ -48,89 +48,128 @@ export default {
directives: {
background: '--fg',
shadow: ['--buttonDefaultShadow', '--buttonDefaultBevel'],
roundness: 3
}
roundness: 3,
},
},
{
state: ['hover'],
variant: 'danger',
directives: {
shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel']
}
background: '--cRed',
},
},
{
state: ['focused'],
variant: 'transparent',
directives: {
shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel']
}
},
{
state: ['pressed'],
directives: {
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
}
},
{
state: ['pressed', 'hover'],
directives: {
shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow']
}
},
{
state: ['toggled'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel']
}
},
{
state: ['toggled', 'hover'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
}
},
{
state: ['toggled', 'focused'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel']
}
},
{
state: ['toggled', 'disabled'],
directives: {
background: '$blend(--accent 0.25 --parent)',
shadow: ['--buttonPressedBevel']
}
},
{
state: ['disabled'],
directives: {
background: '$blend(--inheritedBackground 0.25 --parent)',
shadow: ['--buttonDefaultBevel']
}
opacity: 0.5,
},
},
{
component: 'Text',
parent: {
component: 'Button',
state: ['disabled']
variant: 'transparent',
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
textColor: '--text',
},
},
{
component: 'Icon',
parent: {
component: 'Button',
state: ['disabled']
variant: 'transparent',
},
directives: {
textColor: '--text',
},
},
{
state: ['hover'],
directives: {
shadow: ['--buttonDefaultHoverGlow', '--buttonDefaultBevel'],
},
},
{
state: ['focused'],
directives: {
shadow: ['--buttonDefaultFocusGlow', '--buttonDefaultBevel'],
},
},
{
state: ['pressed'],
directives: {
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'],
},
},
{
state: ['pressed', 'hover'],
directives: {
shadow: ['--buttonPressedBevel', '--buttonDefaultHoverGlow'],
},
},
{
state: ['toggled'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultShadow', '--buttonPressedBevel'],
},
},
{
state: ['toggled', 'hover'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
},
},
{
state: ['toggled', 'focused'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
},
},
{
state: ['toggled', 'hover', 'focused'],
directives: {
background: '--accent,-24.2',
shadow: ['--buttonDefaultHoverGlow', '--buttonPressedBevel'],
},
},
{
state: ['toggled', 'disabled'],
directives: {
background: '$blend(--accent 0.25 --parent)',
shadow: ['--buttonPressedBevel'],
},
},
{
state: ['disabled'],
directives: {
background: '$blend(--inheritedBackground 0.25 --parent)',
shadow: ['--buttonDefaultBevel'],
},
},
{
component: 'Text',
parent: {
component: 'Button',
state: ['disabled'],
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
}
]
textOpacityMode: 'blend',
},
},
{
component: 'Icon',
parent: {
component: 'Button',
state: ['disabled'],
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend',
},
},
],
}

View file

@ -7,91 +7,86 @@ export default {
toggled: '.toggled',
disabled: ':disabled',
hover: ':is(:hover, :focus-visible):not(:disabled)',
focused: ':focus-within:not(:is(:focus-visible))'
focused: ':focus-within:not(:is(:focus-visible))',
},
validInnerComponents: [
'Text',
'Link',
'Icon',
'Badge'
],
validInnerComponents: ['Text', 'Link', 'Icon', 'Badge'],
defaultRules: [
{
directives: {
shadow: []
}
shadow: [],
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['hover']
state: ['hover'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['toggled']
state: ['toggled'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['toggled', 'hover']
state: ['toggled', 'hover'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['toggled', 'focused']
state: ['toggled', 'focused'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['toggled', 'focused', 'hover']
state: ['toggled', 'focused', 'hover'],
},
directives: {
textColor: '--parent--text'
}
textColor: '--parent--text',
},
},
{
component: 'Text',
parent: {
component: 'ButtonUnstyled',
state: ['disabled']
state: ['disabled'],
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
textOpacityMode: 'blend',
},
},
{
component: 'Icon',
parent: {
component: 'ButtonUnstyled',
state: ['disabled']
state: ['disabled'],
},
directives: {
textOpacity: 0.25,
textOpacityMode: 'blend'
}
}
]
textOpacityMode: 'blend',
},
},
],
}

View file

@ -1,25 +1,26 @@
import _ from 'lodash'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import { mapGetters, mapState } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import ChatMessage from '../chat_message/chat_message.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import ChatTitle from '../chat_title/chat_title.vue'
import chatService from '../../services/chat_service/chat_service.js'
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
import { getScrollPosition, getNewTopPosition, isBottomedOut, isScrollable } from './chat_layout_utils.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faChevronDown,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { mapGetters, mapState } from 'vuex'
library.add(
faChevronDown,
faChevronLeft
)
import { useInterfaceStore } from 'src/stores/interface.js'
import { WSConnectionStatus } from '../../services/api/api.service.js'
import chatService from '../../services/chat_service/chat_service.js'
import { buildFakeMessage } from '../../services/chat_utils/chat_utils.js'
import { promiseInterval } from '../../services/promise_interval/promise_interval.js'
import ChatMessage from '../chat_message/chat_message.vue'
import ChatTitle from '../chat_title/chat_title.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import {
getNewTopPosition,
getScrollPosition,
isBottomedOut,
isScrollable,
} from './chat_layout_utils.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons'
library.add(faChevronDown, faChevronLeft)
const BOTTOMED_OUT_OFFSET = 10
const JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET = 10
@ -31,78 +32,95 @@ const Chat = {
components: {
ChatMessage,
ChatTitle,
PostStatusForm
PostStatusForm,
},
data () {
data() {
return {
jumpToBottomButtonVisible: false,
hoveredMessageChainId: undefined,
lastScrollPosition: {},
scrollableContainerHeight: '100%',
errorLoadingChat: false,
messageRetriers: {}
messageRetriers: {},
}
},
created () {
created() {
this.startFetching()
window.addEventListener('resize', this.handleResize)
},
mounted () {
mounted() {
window.addEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') {
document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
document.addEventListener(
'visibilitychange',
this.handleVisibilityChange,
false,
)
}
this.$nextTick(() => {
this.handleResize()
})
},
unmounted () {
unmounted() {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleResize)
if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
if (typeof document.hidden !== 'undefined')
document.removeEventListener(
'visibilitychange',
this.handleVisibilityChange,
false,
)
this.$store.dispatch('clearCurrentChat')
},
computed: {
recipient () {
recipient() {
return this.currentChat && this.currentChat.account
},
recipientId () {
recipientId() {
return this.$route.params.recipient_id
},
formPlaceholder () {
formPlaceholder() {
if (this.recipient) {
return this.$t('chats.message_user', { nickname: this.recipient.screen_name_ui })
return this.$t('chats.message_user', {
nickname: this.recipient.screen_name_ui,
})
} else {
return ''
}
},
chatViewItems () {
chatViewItems() {
return chatService.getView(this.currentChatMessageService)
},
newMessageCount () {
return this.currentChatMessageService && this.currentChatMessageService.newMessageCount
newMessageCount() {
return (
this.currentChatMessageService &&
this.currentChatMessageService.newMessageCount
)
},
streamingEnabled () {
return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED
streamingEnabled() {
return (
this.mergedConfig.useStreamingApi &&
this.mastoUserSocketStatus === WSConnectionStatus.JOINED
)
},
...mapGetters([
'currentChat',
'currentChatMessageService',
'findOpenedChatByRecipientId',
'mergedConfig'
'mergedConfig',
]),
...mapPiniaState(useInterfaceStore, {
mobileLayout: store => store.layoutType === 'mobile'
mobileLayout: (store) => store.layoutType === 'mobile',
}),
...mapState({
backendInteractor: state => state.api.backendInteractor,
mastoUserSocketStatus: state => state.api.mastoUserSocketStatus,
currentUser: state => state.users.currentUser
})
backendInteractor: (state) => state.api.backendInteractor,
mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
currentUser: (state) => state.users.currentUser,
}),
},
watch: {
chatViewItems () {
chatViewItems() {
// We don't want to scroll to the bottom on a new message when the user is viewing older messages.
// Therefore we need to know whether the scroll position was at the bottom before the DOM update.
const bottomedOutBeforeUpdate = this.bottomedOut(BOTTOMED_OUT_OFFSET)
@ -115,23 +133,23 @@ const Chat = {
$route: function () {
this.startFetching()
},
mastoUserSocketStatus (newValue) {
mastoUserSocketStatus(newValue) {
if (newValue === WSConnectionStatus.JOINED) {
this.fetchChat({ isFirstFetch: true })
}
}
},
},
methods: {
// Used to animate the avatar near the first message of the message chain when any message belonging to the chain is hovered
onMessageHover ({ isHovered, messageChainId }) {
onMessageHover({ isHovered, messageChainId }) {
this.hoveredMessageChainId = isHovered ? messageChainId : undefined
},
onFilesDropped () {
onFilesDropped() {
this.$nextTick(() => {
this.handleResize()
})
},
handleVisibilityChange () {
handleVisibilityChange() {
this.$nextTick(() => {
if (!document.hidden && this.bottomedOut(BOTTOMED_OUT_OFFSET)) {
this.scrollDown({ forceRead: true })
@ -139,7 +157,7 @@ const Chat = {
})
},
// "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
handleResize (opts = {}) {
handleResize(opts = {}) {
const { delayed = false } = opts
if (delayed) {
@ -160,40 +178,56 @@ const Chat = {
this.lastScrollPosition = getScrollPosition()
})
},
scrollDown (options = {}) {
scrollDown(options = {}) {
const { behavior = 'auto', forceRead = false } = options
this.$nextTick(() => {
window.scrollTo({ top: document.documentElement.scrollHeight, behavior })
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior,
})
})
if (forceRead) {
this.readChat()
}
},
readChat () {
if (!(this.currentChatMessageService && this.currentChatMessageService.maxId)) { return }
if (document.hidden) { return }
readChat() {
if (
!(
this.currentChatMessageService && this.currentChatMessageService.maxId
)
) {
return
}
if (document.hidden) {
return
}
const lastReadId = this.currentChatMessageService.maxId
this.$store.dispatch('readChat', {
id: this.currentChat.id,
lastReadId
lastReadId,
})
},
bottomedOut (offset) {
bottomedOut(offset) {
return isBottomedOut(offset)
},
reachedTop () {
reachedTop() {
return window.scrollY <= 0
},
cullOlderCheck () {
cullOlderCheck() {
window.setTimeout(() => {
if (this.bottomedOut(JUMP_TO_BOTTOM_BUTTON_VISIBILITY_OFFSET)) {
this.$store.dispatch('cullOlderMessages', this.currentChatMessageService.chatId)
this.$store.dispatch(
'cullOlderMessages',
this.currentChatMessageService.chatId,
)
}
}, 5000)
},
handleScroll: _.throttle(function () {
this.lastScrollPosition = getScrollPosition()
if (!this.currentChat) { return }
if (!this.currentChat) {
return
}
if (this.reachedTop()) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
@ -213,22 +247,27 @@ const Chat = {
this.jumpToBottomButtonVisible = true
}
}, 200),
handleScrollUp (positionBeforeLoading) {
handleScrollUp(positionBeforeLoading) {
const positionAfterLoading = getScrollPosition()
window.scrollTo({
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading)
top: getNewTopPosition(positionBeforeLoading, positionAfterLoading),
})
},
fetchChat ({ isFirstFetch = false, fetchLatest = false, maxId }) {
fetchChat({ isFirstFetch = false, fetchLatest = false, maxId }) {
const chatMessageService = this.currentChatMessageService
if (!chatMessageService) { return }
if (fetchLatest && this.streamingEnabled) { return }
if (!chatMessageService) {
return
}
if (fetchLatest && this.streamingEnabled) {
return
}
const chatId = chatMessageService.chatId
const fetchOlderMessages = !!maxId
const sinceId = fetchLatest && chatMessageService.maxId
return this.backendInteractor.chatMessages({ id: chatId, maxId, sinceId })
return this.backendInteractor
.chatMessages({ id: chatId, maxId, sinceId })
.then((messages) => {
// Clear the current chat in case we're recovering from a ws connection loss.
if (isFirstFetch) {
@ -236,28 +275,34 @@ const Chat = {
}
const positionBeforeUpdate = getScrollPosition()
this.$store.dispatch('addChatMessages', { chatId, messages }).then(() => {
this.$nextTick(() => {
if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate)
}
this.$store
.dispatch('addChatMessages', { chatId, messages })
.then(() => {
this.$nextTick(() => {
if (fetchOlderMessages) {
this.handleScrollUp(positionBeforeUpdate)
}
// In vertical screens, the first batch of fetched messages may not always take the
// full height of the scrollable container.
// If this is the case, we want to fetch the messages until the scrollable container
// is fully populated so that the user has the ability to scroll up and load the history.
if (!isScrollable() && messages.length > 0) {
this.fetchChat({ maxId: this.currentChatMessageService.minId })
}
// In vertical screens, the first batch of fetched messages may not always take the
// full height of the scrollable container.
// If this is the case, we want to fetch the messages until the scrollable container
// is fully populated so that the user has the ability to scroll up and load the history.
if (!isScrollable() && messages.length > 0) {
this.fetchChat({
maxId: this.currentChatMessageService.minId,
})
}
})
})
})
})
},
async startFetching () {
async startFetching() {
let chat = this.findOpenedChatByRecipientId(this.recipientId)
if (!chat) {
try {
chat = await this.backendInteractor.getOrCreateChat({ accountId: this.recipientId })
chat = await this.backendInteractor.getOrCreateChat({
accountId: this.recipientId,
})
} catch (e) {
console.error('Error creating or getting a chat', e)
this.errorLoadingChat = true
@ -271,13 +316,14 @@ const Chat = {
this.doStartFetching()
}
},
doStartFetching () {
doStartFetching() {
this.$store.dispatch('startFetchingCurrentChat', {
fetcher: () => promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000)
fetcher: () =>
promiseInterval(() => this.fetchChat({ fetchLatest: true }), 5000),
})
this.fetchChat({ isFirstFetch: true })
},
handleAttachmentPosting () {
handleAttachmentPosting() {
this.$nextTick(() => {
this.handleResize()
// When the posting form size changes because of a media attachment, we need an extra resize
@ -285,11 +331,11 @@ const Chat = {
this.scrollDown({ forceRead: true })
})
},
sendMessage ({ status, media, idempotencyKey }) {
sendMessage({ status, media, idempotencyKey }) {
const params = {
id: this.currentChat.id,
content: status,
idempotencyKey
idempotencyKey,
}
if (media[0]) {
@ -301,52 +347,72 @@ const Chat = {
chatId: this.currentChat.id,
content: status,
userId: this.currentUser.id,
idempotencyKey
idempotencyKey,
})
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
messages: [fakeMessage]
}).then(() => {
this.handleAttachmentPosting()
})
this.$store
.dispatch('addChatMessages', {
chatId: this.currentChat.id,
messages: [fakeMessage],
})
.then(() => {
this.handleAttachmentPosting()
})
return this.doSendMessage({ params, fakeMessage, retriesLeft: MAX_RETRIES })
return this.doSendMessage({
params,
fakeMessage,
retriesLeft: MAX_RETRIES,
})
},
doSendMessage ({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
doSendMessage({ params, fakeMessage, retriesLeft = MAX_RETRIES }) {
if (retriesLeft <= 0) return
this.backendInteractor.sendChatMessage(params)
.then(data => {
this.backendInteractor
.sendChatMessage(params)
.then((data) => {
this.$store.dispatch('addChatMessages', {
chatId: this.currentChat.id,
updateMaxId: false,
messages: [{ ...data, fakeId: fakeMessage.id }]
messages: [{ ...data, fakeId: fakeMessage.id }],
})
return data
})
.catch(error => {
.catch((error) => {
console.error('Error sending message', error)
this.$store.dispatch('handleMessageError', {
chatId: this.currentChat.id,
fakeId: fakeMessage.id,
isRetry: retriesLeft !== MAX_RETRIES
isRetry: retriesLeft !== MAX_RETRIES,
})
if ((error.statusCode >= 500 && error.statusCode < 600) || error.message === 'Failed to fetch') {
this.messageRetriers[fakeMessage.id] = setTimeout(() => {
this.doSendMessage({ params, fakeMessage, retriesLeft: retriesLeft - 1 })
}, 1000 * (2 ** (MAX_RETRIES - retriesLeft)))
if (
(error.statusCode >= 500 && error.statusCode < 600) ||
error.message === 'Failed to fetch'
) {
this.messageRetriers[fakeMessage.id] = setTimeout(
() => {
this.doSendMessage({
params,
fakeMessage,
retriesLeft: retriesLeft - 1,
})
},
1000 * 2 ** (MAX_RETRIES - retriesLeft),
)
}
return {}
})
return Promise.resolve(fakeMessage)
},
goBack () {
this.$router.push({ name: 'chats', params: { username: this.currentUser.screen_name } })
}
}
goBack() {
this.$router.push({
name: 'chats',
params: { username: this.currentUser.screen_name },
})
},
},
}
export default Chat

View file

@ -1,19 +1,13 @@
export default {
name: 'Chat',
selector: '.chat-message-list',
validInnerComponents: [
'Text',
'Link',
'Icon',
'Avatar',
'ChatMessage'
],
validInnerComponents: ['Text', 'Link', 'Icon', 'Avatar', 'ChatMessage'],
defaultRules: [
{
directives: {
background: '--bg',
blur: '5px'
}
}
]
blur: '5px',
},
},
],
}

View file

@ -3,14 +3,17 @@ export const getScrollPosition = () => {
return {
scrollTop: window.scrollY,
scrollHeight: document.documentElement.scrollHeight,
offsetHeight: window.innerHeight
offsetHeight: window.innerHeight,
}
}
// A helper function that is used to keep the scroll position fixed as the new elements are added to the top
// Takes two scroll positions, before and after the update.
export const getNewTopPosition = (previousPosition, newPosition) => {
return previousPosition.scrollTop + (newPosition.scrollHeight - previousPosition.scrollHeight)
return (
previousPosition.scrollTop +
(newPosition.scrollHeight - previousPosition.scrollHeight)
)
}
export const isBottomedOut = (offset = 0) => {

View file

@ -1,4 +1,5 @@
import { mapState, mapGetters } from 'vuex'
import { mapGetters, mapState } from 'vuex'
import ChatListItem from '../chat_list_item/chat_list_item.vue'
import ChatNew from '../chat_new/chat_new.vue'
import List from '../list/list.vue'
@ -7,31 +8,31 @@ const ChatList = {
components: {
ChatListItem,
List,
ChatNew
ChatNew,
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
currentUser: (state) => state.users.currentUser,
}),
...mapGetters(['sortedChatList'])
...mapGetters(['sortedChatList']),
},
data () {
data() {
return {
isNew: false
isNew: false,
}
},
created () {
created() {
this.$store.dispatch('fetchChats', { latest: true })
},
methods: {
cancelNewChat () {
cancelNewChat() {
this.isNew = false
this.$store.dispatch('fetchChats', { latest: true })
},
newChat () {
newChat() {
this.isNew = true
}
}
},
},
}
export default ChatList

View file

@ -1,31 +1,34 @@
import { mapState } from 'vuex'
import StatusBody from '../status_content/status_content.vue'
import fileType from 'src/services/file_type/file_type.service'
import UserAvatar from '../user_avatar/user_avatar.vue'
import AvatarList from '../avatar_list/avatar_list.vue'
import Timeago from '../timeago/timeago.vue'
import ChatTitle from '../chat_title/chat_title.vue'
import StatusBody from '../status_content/status_content.vue'
import Timeago from '../timeago/timeago.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
const ChatListItem = {
name: 'ChatListItem',
props: [
'chat'
],
props: ['chat'],
components: {
UserAvatar,
AvatarList,
Timeago,
ChatTitle,
StatusBody
StatusBody,
},
computed: {
...mapState({
currentUser: state => state.users.currentUser
currentUser: (state) => state.users.currentUser,
}),
attachmentInfo () {
if (this.chat.lastMessage.attachments.length === 0) { return }
attachmentInfo() {
if (this.chat.lastMessage.attachments.length === 0) {
return
}
const types = this.chat.lastMessage.attachments.map(file => fileType.fileType(file.mimetype))
const types = this.chat.lastMessage.attachments.map((file) =>
fileType.fileType(file.mimetype),
)
if (types.includes('video')) {
return this.$t('file_type.video')
} else if (types.includes('audio')) {
@ -36,34 +39,36 @@ const ChatListItem = {
return this.$t('file_type.file')
}
},
messageForStatusContent () {
messageForStatusContent() {
const message = this.chat.lastMessage
const messageEmojis = message ? message.emojis : []
const isYou = message && message.account_id === this.currentUser.id
const content = message ? (this.attachmentInfo || message.content) : ''
const messagePreview = isYou ? `<i>${this.$t('chats.you')}</i> ${content}` : content
const content = message ? this.attachmentInfo || message.content : ''
const messagePreview = isYou
? `<i>${this.$t('chats.you')}</i> ${content}`
: content
return {
summary: '',
emojis: messageEmojis,
raw_html: messagePreview,
text: messagePreview,
attachments: []
attachments: [],
}
}
},
},
methods: {
openChat () {
openChat() {
if (this.chat.id) {
this.$router.push({
name: 'chat',
params: {
username: this.currentUser.screen_name,
recipient_id: this.chat.account.id
}
recipient_id: this.chat.account.id,
},
})
}
}
}
},
},
}
export default ChatListItem

View file

@ -1,24 +1,20 @@
import { mapState, mapGetters } from 'vuex'
import { mapState as mapPiniaState } from 'pinia'
import Popover from '../popover/popover.vue'
import { defineAsyncComponent } from 'vue'
import { mapGetters, mapState } from 'vuex'
import { useInterfaceStore } from 'src/stores/interface'
import Attachment from '../attachment/attachment.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
import Gallery from '../gallery/gallery.vue'
import LinkPreview from '../link-preview/link-preview.vue'
import Popover from '../popover/popover.vue'
import StatusContent from '../status_content/status_content.vue'
import ChatMessageDate from '../chat_message_date/chat_message_date.vue'
import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes,
faEllipsisH
} from '@fortawesome/free-solid-svg-icons'
import { useInterfaceStore } from 'src/stores/interface'
import UserAvatar from '../user_avatar/user_avatar.vue'
library.add(
faTimes,
faEllipsisH
)
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEllipsisH, faTimes } from '@fortawesome/free-solid-svg-icons'
library.add(faTimes, faEllipsisH)
const ChatMessage = {
name: 'ChatMessage',
@ -27,7 +23,7 @@ const ChatMessage = {
'edited',
'noHeading',
'chatViewItem',
'hoveredMessageChain'
'hoveredMessageChain',
],
emits: ['hover'],
components: {
@ -38,73 +34,82 @@ const ChatMessage = {
Gallery,
LinkPreview,
ChatMessageDate,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
UserPopover: defineAsyncComponent(
() => import('../user_popover/user_popover.vue'),
),
},
computed: {
// Returns HH:MM (hours and minutes) in local time.
createdAt () {
createdAt() {
const time = this.chatViewItem.data.created_at
return time.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit', hour12: false })
return time.toLocaleTimeString('en', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
})
},
isCurrentUser () {
isCurrentUser() {
return this.message.account_id === this.currentUser.id
},
message () {
message() {
return this.chatViewItem.data
},
isMessage () {
isMessage() {
return this.chatViewItem.type === 'message'
},
messageForStatusContent () {
messageForStatusContent() {
return {
summary: '',
emojis: this.message.emojis,
raw_html: this.message.content || '',
text: this.message.content || '',
attachments: this.message.attachments
attachments: this.message.attachments,
}
},
hasAttachment () {
hasAttachment() {
return this.message.attachments.length > 0
},
...mapPiniaState(useInterfaceStore, {
betterShadow: store => store.browserSupport.cssFilter
betterShadow: (store) => store.browserSupport.cssFilter,
}),
...mapState({
currentUser: state => state.users.currentUser,
restrictedNicknames: state => state.instance.restrictedNicknames
currentUser: (state) => state.users.currentUser,
restrictedNicknames: (state) => state.instance.restrictedNicknames,
}),
popoverMarginStyle () {
popoverMarginStyle() {
if (this.isCurrentUser) {
return {}
} else {
return { left: 50 }
}
},
...mapGetters(['mergedConfig', 'findUser'])
...mapGetters(['mergedConfig', 'findUser']),
},
data () {
data() {
return {
hovered: false,
menuOpened: false
menuOpened: false,
}
},
methods: {
onHover (bool) {
this.$emit('hover', { isHovered: bool, messageChainId: this.chatViewItem.messageChainId })
onHover(bool) {
this.$emit('hover', {
isHovered: bool,
messageChainId: this.chatViewItem.messageChainId,
})
},
async deleteMessage () {
async deleteMessage() {
const confirmed = window.confirm(this.$t('chats.delete_confirm'))
if (confirmed) {
await this.$store.dispatch('deleteChatMessage', {
messageId: this.chatViewItem.data.id,
chatId: this.chatViewItem.data.chat_id
chatId: this.chatViewItem.data.chat_id,
})
}
this.hovered = false
this.menuOpened = false
}
}
},
},
}
export default ChatMessage

View file

@ -2,29 +2,21 @@ export default {
name: 'ChatMessage',
selector: '.chat-message',
variants: {
outgoing: '.outgoing'
outgoing: '.outgoing',
},
validInnerComponents: [
'Text',
'Icon',
'Border',
'Button',
'RichContent',
'Attachment',
'PollGraph'
],
validInnerComponents: ['Text', 'Icon', 'Border', 'PollGraph'],
defaultRules: [
{
directives: {
background: '--bg, 2',
backgroundNoCssColor: 'yes'
}
backgroundNoCssColor: 'yes',
},
},
{
variant: 'outgoing',
directives: {
background: '--bg, 5'
}
}
]
background: '--bg, 5',
},
},
],
}

View file

@ -11,16 +11,19 @@ export default {
name: 'Timeago',
props: ['date'],
computed: {
displayDate () {
displayDate() {
const today = new Date()
today.setHours(0, 0, 0, 0)
if (this.date.getTime() === today.getTime()) {
return this.$t('display_date.today')
} else {
return this.date.toLocaleDateString(localeService.internalToBrowserLocale(this.$i18n.locale), { day: 'numeric', month: 'long' })
return this.date.toLocaleDateString(
localeService.internalToBrowserLocale(this.$i18n.locale),
{ day: 'numeric', month: 'long' },
)
}
}
}
},
},
}
</script>

View file

@ -1,39 +1,35 @@
import { mapState, mapGetters } from 'vuex'
import { mapGetters, mapState } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSearch,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
library.add(
faSearch,
faChevronLeft
)
import { library } from '@fortawesome/fontawesome-svg-core'
import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
library.add(faSearch, faChevronLeft)
const chatNew = {
components: {
BasicUserCard,
UserAvatar
UserAvatar,
},
data () {
data() {
return {
suggestions: [],
userIds: [],
loading: false,
query: ''
query: '',
}
},
async created () {
async created() {
const { chats } = await this.backendInteractor.chats()
chats.forEach(chat => this.suggestions.push(chat.account))
chats.forEach((chat) => this.suggestions.push(chat.account))
},
computed: {
users () {
return this.userIds.map(userId => this.findUser(userId))
users() {
return this.userIds.map((userId) => this.findUser(userId))
},
availableUsers () {
availableUsers() {
if (this.query.length !== 0) {
return this.users
} else {
@ -41,29 +37,29 @@ const chatNew = {
}
},
...mapState({
currentUser: state => state.users.currentUser,
backendInteractor: state => state.api.backendInteractor
currentUser: (state) => state.users.currentUser,
backendInteractor: (state) => state.api.backendInteractor,
}),
...mapGetters(['findUser'])
...mapGetters(['findUser']),
},
methods: {
goBack () {
goBack() {
this.$emit('cancel')
},
goToChat (user) {
goToChat(user) {
this.$router.push({ name: 'chat', params: { recipient_id: user.id } })
},
onInput () {
onInput() {
this.search(this.query)
},
addUser (user) {
addUser(user) {
this.selectedUserIds.push(user.id)
this.query = ''
},
removeUser (userId) {
this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
removeUser(userId) {
this.selectedUserIds = this.selectedUserIds.filter((id) => id !== userId)
},
search (query) {
search(query) {
if (!query) {
this.loading = false
return
@ -71,13 +67,14 @@ const chatNew = {
this.loading = true
this.userIds = []
this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts' })
.then(data => {
this.$store
.dispatch('search', { q: query, resolve: true, type: 'accounts' })
.then((data) => {
this.loading = false
this.userIds = data.accounts.map(a => a.id)
this.userIds = data.accounts.map((a) => a.id)
})
}
}
},
},
}
export default chatNew

View file

@ -1,23 +1,24 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import { defineAsyncComponent } from 'vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import UserAvatar from '../user_avatar/user_avatar.vue'
export default {
name: 'ChatTitle',
components: {
UserAvatar,
RichContent,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
UserPopover: defineAsyncComponent(
() => import('../user_popover/user_popover.vue'),
),
},
props: [
'user', 'withAvatar'
],
props: ['user', 'withAvatar'],
computed: {
title () {
title() {
return this.user ? this.user.screen_name_ui : ''
},
htmlTitle () {
htmlTitle() {
return this.user ? this.user.name_html : ''
}
}
},
},
}

View file

@ -19,6 +19,7 @@
:title="'@'+(user && user.screen_name_ui)"
:html="htmlTitle"
:emoji="user.emoji || []"
:is-local="user.is_local"
/>
</div>
</template>

View file

@ -1,7 +1,7 @@
<template>
<label
class="checkbox"
:class="[{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }, radio ? '-radio' : '-checkbox']"
:class="[{ ['-disabled']: disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }, radio ? '-radio' : '-checkbox']"
>
<span
v-if="!!$slots.before"
@ -36,30 +36,25 @@
<script>
export default {
props: [
'radio',
'modelValue',
'indeterminate',
'disabled'
],
props: ['radio', 'modelValue', 'indeterminate', 'disabled'],
emits: ['update:modelValue'],
data: (vm) => ({
indeterminateTransitionFix: vm.indeterminate
indeterminateTransitionFix: vm.indeterminate,
}),
watch: {
indeterminate (e) {
indeterminate(e) {
if (e) {
this.indeterminateTransitionFix = true
}
}
},
},
methods: {
onTransitionEnd () {
onTransitionEnd() {
if (!this.indeterminate) {
this.indeterminateTransitionFix = false
}
}
}
},
},
}
</script>
@ -123,7 +118,7 @@ export default {
.disabled {
.checkbox-indicator::before {
background-color: var(--background);
background-color: transparent;
}
}

View file

@ -1,18 +1,27 @@
.color-input {
display: inline-flex;
flex-wrap: wrap;
max-width: 10em;
&.-compact {
max-width: none;
}
.label {
flex: 1 1 auto;
grid-area: label;
}
.opt {
grid-area: checkbox;
margin-right: 0.5em;
}
&-field.input {
display: inline-flex;
flex: 0 0 0;
max-width: 9em;
flex: 1 1 10em;
max-width: 10em;
grid-area: input;
display: flex;
align-items: stretch;
input {

View file

@ -1,7 +1,7 @@
<template>
<div
class="color-input style-control"
:class="{ disabled: !present || disabled }"
:class="{ disabled: !present || disabled, '-compact': compact }"
>
<label
:for="name"
@ -19,7 +19,7 @@
/>
<div
class="input color-input-field"
:class="{ disabled: !present || disabled }"
:class="{ disabled: !present || disabled, unstyled }"
>
<input
:id="name + '-t'"
@ -64,86 +64,95 @@
</div>
</template>
<script>
import Checkbox from '../checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { throttle } from 'lodash'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEyeDropper
} from '@fortawesome/free-solid-svg-icons'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import Checkbox from '../checkbox/checkbox.vue'
library.add(
faEyeDropper
)
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEyeDropper } from '@fortawesome/free-solid-svg-icons'
library.add(faEyeDropper)
export default {
components: {
Checkbox
Checkbox,
},
props: {
// Name of color, used for identifying
name: {
required: true,
type: String
type: String,
},
// Readable label
label: {
required: true,
type: String
type: String,
},
// use unstyled, uh, style
unstyled: {
required: false,
type: Boolean,
},
// Color value, should be required but vue cannot tell the difference
// between "property missing" and "property set to undefined"
modelValue: {
required: false,
type: String,
default: undefined
default: undefined,
},
// Color fallback to use when value is not defeind
fallback: {
required: false,
type: String,
default: undefined
default: undefined,
},
// Disable the control
disabled: {
required: false,
type: Boolean,
default: false
default: false,
},
// Show "optional" tickbox, for when value might become mandatory
showOptionalCheckbox: {
required: false,
type: Boolean,
default: true
default: true,
},
// Force "optional" tickbox to hide
hideOptionalCheckbox: {
required: false,
type: Boolean,
default: false
}
default: false,
},
compact: {
required: false,
type: Boolean,
},
},
emits: ['update:modelValue'],
computed: {
present () {
present() {
return typeof this.modelValue !== 'undefined'
},
validColor () {
validColor() {
return hex2rgb(this.modelValue || this.fallback)
},
transparentColor () {
transparentColor() {
return this.modelValue === 'transparent'
},
computedColor () {
return this.modelValue && (this.modelValue.startsWith('--') || this.modelValue.startsWith('$'))
}
computedColor() {
return (
this.modelValue &&
(this.modelValue.startsWith('--') || this.modelValue.startsWith('$'))
)
},
},
methods: {
updateValue: throttle(function (value) {
this.$emit('update:modelValue', value)
}, 100)
}
}, 100),
},
}
</script>
<style lang="scss" src="./color_input.scss"></style>

View file

@ -0,0 +1,86 @@
import Checkbox from 'src/components/checkbox/checkbox.vue'
import ColorInput from 'src/components/color_input/color_input.vue'
import genRandomSeed from 'src/services/random_seed/random_seed.service.js'
import {
adoptStyleSheets,
createStyleSheet,
} from 'src/services/style_setter/style_setter.js'
export default {
components: {
Checkbox,
ColorInput,
},
props: [
'shadow',
'shadowControl',
'previewClass',
'previewStyle',
'previewCss',
'disabled',
'invalid',
'noColorControl',
],
emits: ['update:shadow'],
data() {
return {
colorOverride: undefined,
lightGrid: false,
zoom: 100,
randomSeed: genRandomSeed(),
}
},
mounted() {
this.update()
},
computed: {
hideControls() {
return typeof this.shadow === 'string'
},
},
watch: {
previewCss() {
this.update()
},
previewStyle() {
this.update()
},
zoom() {
this.update()
},
},
methods: {
updateProperty(axis, value) {
this.$emit('update:shadow', { axis, value: Number(value) })
},
update() {
const sheet = createStyleSheet('style-component-preview', 90)
sheet.clear()
const result = [this.previewCss]
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
const styleRule = [
'#component-preview-',
this.randomSeed,
' {\n',
'.preview-block {\n',
`zoom: ${this.zoom / 100};`,
this.previewStyle,
'\n}',
'\n}',
].join('')
sheet.addRule(styleRule)
sheet.addRule(
['#component-preview-', this.randomSeed, ' {\n', ...result, '\n}'].join(
'',
),
)
sheet.ready = true
adoptStyleSheets()
},
},
}

View file

@ -0,0 +1,151 @@
.ComponentPreview {
display: grid;
grid-template-columns: 1em 1fr 1fr 1em;
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"x-slide x-slide x-slide . "
"x-num x-num y-num y-num "
"assists assists assists assists";
grid-gap: 0.5em;
&:not(.-shadow-controls) {
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"assists assists assists assists";
grid-template-rows: 2em 1fr 1fr 1fr max-content;
}
.header {
grid-area: header;
place-self: baseline center;
line-height: 2;
}
.invalid-container {
position: absolute;
inset: 0;
display: grid;
place-items: center center;
background-color: rgb(100 0 0 / 50%);
.alert {
padding: 0.5em 1em;
}
}
.assists {
grid-area: assists;
display: grid;
grid-auto-flow: row;
grid-auto-rows: 2em;
grid-gap: 0.5em;
}
.input-light-grid {
justify-self: center;
}
.input-number {
min-width: 2em;
}
.x-shift-number {
grid-area: x-num;
justify-self: right;
}
.y-shift-number {
grid-area: y-num;
justify-self: left;
}
.x-shift-number,
.y-shift-number {
input {
max-width: 4em;
}
}
.x-shift-slider {
grid-area: x-slide;
height: auto;
align-self: start;
min-width: 10em;
}
.y-shift-slider {
grid-area: y-slide;
writing-mode: vertical-lr;
justify-self: left;
min-height: 10em;
}
.x-shift-slider,
.y-shift-slider {
padding: 0;
}
.preview-window {
--__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgb(102 102 102 / 20%);
--__grid-color2-disabled: rgb(153 153 153 / 20%);
&.-light-grid {
--__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgb(205 205 205 / 20%);
--__grid-color2-disabled: rgb(255 255 255 / 20%);
}
position: relative;
grid-area: preview;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 10em;
min-height: 10em;
background-color: var(--__grid-color2);
background-image:
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
&.disabled {
background-color: var(--__grid-color2-disabled);
background-image:
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
}
.preview-block {
background: var(--background, var(--bg));
display: flex;
justify-content: center;
align-items: center;
min-width: 33%;
min-height: 33%;
max-width: 80%;
max-height: 80%;
border-width: 0;
border-style: solid;
border-color: var(--border);
border-radius: var(--roundness);
box-shadow: var(--shadow);
}
}
}

View file

@ -1,14 +1,9 @@
<template>
<div
:id="'component-preview-' + randomSeed"
class="ComponentPreview"
:class="{ '-shadow-controls': shadowControl }"
>
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
<component
:is="'style'"
v-html="previewCss"
/>
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
<label
v-show="shadowControl"
role="heading"
@ -74,7 +69,6 @@
<div
class="preview-block"
:class="previewClass"
:style="style"
>
{{ $t('settings.style.themes3.editor.test_string') }}
</div>
@ -110,209 +104,12 @@
v-model="colorOverride"
class="input-color-input"
fallback="#606060"
:compact="true"
:label="$t('settings.style.shadows.color_override')"
/>
</div>
</div>
</template>
<script>
import Checkbox from 'src/components/checkbox/checkbox.vue'
import ColorInput from 'src/components/color_input/color_input.vue'
export default {
components: {
Checkbox,
ColorInput
},
props: [
'shadow',
'shadowControl',
'previewClass',
'previewStyle',
'previewCss',
'disabled',
'invalid',
'noColorControl'
],
emits: ['update:shadow'],
data () {
return {
colorOverride: undefined,
lightGrid: false,
zoom: 100
}
},
computed: {
style () {
const result = [
this.previewStyle,
`zoom: ${this.zoom / 100}`
]
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
return result
},
hideControls () {
return typeof this.shadow === 'string'
}
},
methods: {
updateProperty (axis, value) {
this.$emit('update:shadow', { axis, value: Number(value) })
}
}
}
</script>
<style lang="scss">
.ComponentPreview {
display: grid;
grid-template-columns: 1em 1fr 1fr 1em;
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"x-slide x-slide x-slide . "
"x-num x-num y-num y-num "
"assists assists assists assists";
grid-gap: 0.5em;
&:not(.-shadow-controls) {
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"assists assists assists assists";
grid-template-rows: 2em 1fr 1fr 1fr max-content;
}
.header {
grid-area: header;
place-self: baseline center;
line-height: 2;
}
.invalid-container {
position: absolute;
inset: 0;
display: grid;
place-items: center center;
background-color: rgb(100 0 0 / 50%);
.alert {
padding: 0.5em 1em;
}
}
.assists {
grid-area: assists;
display: grid;
grid-auto-flow: row;
grid-auto-rows: 2em;
grid-gap: 0.5em;
}
.input-light-grid {
justify-self: center;
}
.input-number {
min-width: 2em;
}
.x-shift-number {
grid-area: x-num;
justify-self: right;
}
.y-shift-number {
grid-area: y-num;
justify-self: left;
}
.x-shift-number,
.y-shift-number {
input {
max-width: 4em;
}
}
.x-shift-slider {
grid-area: x-slide;
height: auto;
align-self: start;
min-width: 10em;
}
.y-shift-slider {
grid-area: y-slide;
writing-mode: vertical-lr;
justify-self: left;
min-height: 10em;
}
.x-shift-slider,
.y-shift-slider {
padding: 0;
}
.preview-window {
--__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgb(102 102 102 / 20%);
--__grid-color2-disabled: rgb(153 153 153 / 20%);
&.-light-grid {
--__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgb(205 205 205 / 20%);
--__grid-color2-disabled: rgb(255 255 255 / 20%);
}
position: relative;
grid-area: preview;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 10em;
min-height: 10em;
background-color: var(--__grid-color2);
background-image:
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
&.disabled {
background-color: var(--__grid-color2-disabled);
background-image:
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
}
.preview-block {
background: var(--background, var(--bg));
display: flex;
justify-content: center;
align-items: center;
min-width: 33%;
min-height: 33%;
max-width: 80%;
max-height: 80%;
border-width: 0;
border-style: solid;
border-color: var(--border);
border-radius: var(--roundness);
box-shadow: var(--shadow);
}
}
}
</style>
<script src="./component_preview.js" />
<style src="./component_preview.scss" lang="scss" />

View file

@ -9,30 +9,29 @@ import DialogModal from '../dialog_modal/dialog_modal.vue'
*/
const ConfirmModal = {
components: {
DialogModal
DialogModal,
},
props: {
title: {
type: String
type: String,
},
cancelText: {
type: String
type: String,
},
confirmText: {
type: String
}
type: String,
},
},
emits: ['cancelled', 'accepted'],
computed: {
},
computed: {},
methods: {
onCancel () {
onCancel() {
this.$emit('cancelled')
},
onAccept () {
onAccept() {
this.$emit('accepted')
}
}
},
},
}
export default ConfirmModal

View file

@ -11,6 +11,7 @@
<slot />
<template #footer>
<slot name="footerLeft" />
<button
class="btn button-default"
@click.prevent="onAccept"

View file

@ -1,109 +1,87 @@
import { unitToSeconds } from 'src/services/date_utils/date_utils.js'
import { mapGetters } from 'vuex'
import ConfirmModal from './confirm_modal.vue'
import Select from 'src/components/select/select.vue'
import ConfirmModal from './confirm_modal.vue'
export default {
props: ['type', 'user', 'status'],
emits: ['hide', 'show', 'muted'],
data: () => ({
showing: false,
muteExpiryAmount: 2,
muteExpiryUnit: 'hours'
}),
components: {
ConfirmModal,
Select
Select,
},
computed: {
muteExpiryValue () {
unitToSeconds(this.muteExpiryUnit, this.muteExpiryAmount)
},
muteExpiryUnits () {
return ['minutes', 'hours', 'days']
},
domain () {
domain() {
return this.user.fqn.split('@')[1]
},
keypath () {
keypath() {
if (this.type === 'domain') {
return 'status.mute_domain_confirm'
return 'user_card.mute_domain_confirm'
} else if (this.type === 'conversation') {
return 'status.mute_conversation_confirm'
} else {
return 'user_card.mute_confirm'
return 'user_card.mute_conversation_confirm'
}
},
userIsMuted () {
return this.$store.getters.relationship(this.user.id).muting
},
conversationIsMuted () {
conversationIsMuted() {
return this.status.conversation_muted
},
domainIsMuted () {
return new Set(this.$store.state.users.currentUser.domainMutes).has(this.domain)
domainIsMuted() {
return new Set(this.$store.state.users.currentUser.domainMutes).has(
this.domain,
)
},
shouldConfirm () {
shouldConfirm() {
switch (this.type) {
case 'domain': {
return this.mergedConfig.modalOnMuteDomain
}
case 'conversation': {
return this.mergedConfig.modalOnMuteConversation
}
default: {
return this.mergedConfig.modalOnMute
// conversation
return this.mergedConfig.modalOnMuteConversation
}
}
},
...mapGetters(['mergedConfig'])
...mapGetters(['mergedConfig']),
},
methods: {
optionallyPrompt () {
optionallyPrompt() {
if (this.shouldConfirm) {
this.show()
} else {
this.doMute()
}
},
show () {
show() {
this.showing = true
this.$emit('show')
},
hide () {
hide() {
this.showing = false
this.$emit('hide')
},
doMute () {
doMute() {
switch (this.type) {
case 'domain': {
if (!this.domainIsMuted) {
this.$store.dispatch('muteDomain', { id: this.domain, expiresIn: this.muteExpiryValue })
this.$store.dispatch('muteDomain', this.domain)
} else {
this.$store.dispatch('unmuteDomain', { id: this.domain })
this.$store.dispatch('unmuteDomain', this.domain)
}
break
}
case 'conversation': {
if (!this.conversationIsMuted) {
this.$store.dispatch('muteConversation', { id: this.status.id, expiresIn: this.muteExpiryValue })
this.$store.dispatch('muteConversation', { id: this.status.id })
} else {
this.$store.dispatch('unmuteConversation', { id: this.status.id })
}
break
}
default: {
if (!this.userIsMuted) {
this.$store.dispatch('muteUser', { id: this.user.id, expiresIn: this.muteExpiryValue })
} else {
this.$store.dispatch('unmuteUser', { id: this.user.id })
}
break
}
}
this.$emit('muted')
this.hide()
}
}
},
},
}

View file

@ -18,36 +18,6 @@
<span v-text="user.screen_name_ui" />
</template>
</i18n-t>
<div
v-if="type !== 'domain'"
class="mute-expiry"
>
<p>
<label>
{{ $t('user_card.mute_duration_prompt') }}
</label>
<input
v-model="muteExpiryAmount"
type="number"
class="input expiry-amount hide-number-spinner"
:min="0"
>
{{ ' ' }}
<Select
v-model="muteExpiryUnit"
unstyled="true"
class="expiry-unit"
>
<option
v-for="unit in muteExpiryUnits"
:key="unit"
:value="unit"
>
{{ $t(`time.unit.${unit}_short`, ['']) }}
</option>
</Select>
</p>
</div>
</confirm-modal>
</template>

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