Compare commits

...

277 commits

Author SHA1 Message Date
a7c6f76ee4
Re-add text shadows on user cards for better visibility 2025-02-05 11:29:40 +09:00
218602a7e8
Merge remote-tracking branch 'upstream/develop' into bnakkoma 2025-02-05 10:06:23 +09:00
floatingghost
2086522d64 Merge pull request '(arguably) improved layouting of user profile page' () from Riedler/akkoma-fe:user-profile-changes into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/403
2025-01-15 21:47:55 +00:00
Weblate
fa294e0003 Merge branch 'origin/develop' into Weblate. 2025-01-05 15:52:29 +00:00
floatingghost
d3fa5cfad0 Merge pull request 'post_status_form: enable sync flush for watcher' () from novenary/akkoma-fe:sticky-drafts into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/414
2025-01-05 15:52:26 +00:00
Weblate
9552287442 Merge branch 'origin/develop' into Weblate. 2025-01-05 15:52:19 +00:00
floatingghost
6b7c8f0def Merge pull request 'Allow using custom source URLs' () from Oneric/akkoma-fe:custom-source into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/421
2025-01-05 15:52:15 +00:00
Weblate
3386692e26 Merge branch 'origin/develop' into Weblate. 2025-01-05 15:51:50 +00:00
floatingghost
ad6bb47003 Merge pull request 'Add visual feedback when clicking translate' () from ilja/akkoma-fe:provide_visual_feedback_when_clicking_translate_button into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/423
2025-01-05 15:51:47 +00:00
ilja
9838545904 Add visual feedback when clicking translate
In a status, we can choose to translate the status (assuming there's a translator enabled on the backend)

It will translate, in practice generally according to detected language, and also provide an option to override the source language.

Translating can take a while, and there wasn't really a visual feedback when it was translating.
Now the translate button will be dissabled while translating.
2024-12-01 14:04:49 +01:00
Weblate
b3f25e5d84 Translated using Weblate (Polish)
Currently translated at 99.7% (1046 of 1049 strings)

Translated using Weblate (Polish)

Currently translated at 99.7% (1046 of 1049 strings)

Co-authored-by: ? <akkoma@mkljczk.pl>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: subtype <subtype@hollow.capital>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/pl/
Translation: Pleroma fe/pleroma-fe
2024-11-22 04:56:24 +00:00
Weblate
248509073e Translated using Weblate (Italian)
Currently translated at 93.8% (985 of 1049 strings)

Co-authored-by: Steffo <akkoma@steffo.eu>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/it/
Translation: Pleroma fe/pleroma-fe
2024-11-22 04:56:24 +00:00
Weblate
a7d6235131 Translated using Weblate (Lithuanian)
Currently translated at 8.1% (86 of 1049 strings)

Translated using Weblate (Lithuanian)

Currently translated at 5.5% (58 of 1049 strings)

Translated using Weblate (Lithuanian)

Currently translated at 1.9% (20 of 1049 strings)

Added translation using Weblate (Lithuanian)

Co-authored-by: Vaclovas Intas <Gateway_31@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/lt/
Translation: Pleroma fe/pleroma-fe
2024-11-22 04:56:24 +00:00
aa5f347ce1
Merge remote-tracking branch 'upstream/develop' into bnakkoma 2024-10-29 05:07:32 +09:00
Oneric
42ba77ebf4 Allow using custom source URLs 2024-10-26 16:32:14 +02:00
floatingghost
4a50b1273d Merge pull request 'fix panel z-index conflicting with heading popover' () from tea/akkoma-fe:fix/panel-z-index into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/422
2024-10-26 03:42:48 +00:00
floatingghost
c76dc6d79e Merge pull request 'Fix redirect on logout' () from Oneric/akkoma-fe:logout-redirect into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/420
2024-10-26 03:42:23 +00:00
floatingghost
cb4c581cde Merge pull request 'Add proper autocomplete prop for TOTP login field' () from tudbut/akkoma-fe:develop into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/424
2024-10-26 03:41:15 +00:00
00ab4aae3b
Fix avatar rendering in theme preview 2024-10-25 10:02:56 +09:00
f198cbcc39
Rename the default theme to BNAkkoma 2024-10-25 09:47:21 +09:00
TudbuT
8231c8f0b6
add proper autocomplete prop for TOTP login field 2024-10-19 19:19:15 +02:00
novenary
ef242a1ddd post_status_form: enable sync flush for watcher
This fixes drafts not clearing after posting a reply.

Vue 3.3.11 changed watchers to stop firing after component unmount.
After posting a reply, the post form is removed, now causing the queued
event to be discarded.
Synchronous flush causes the handler to be called immediately when
changes happen, solving the problem.

The performance impact of this change seems non-existent. Even before,
typing would generate an event for each keystroke. Pasting is atomic.

See: https://github.com/vuejs/core/pull/7181
See: 80e2128d52
Fixes: a7dea2f70f
Fixes: 
2024-10-15 00:16:45 +03:00
tea
35cf3327c8 fix panel z-index conflicting with heading popover
resolves 
2024-10-05 10:59:46 +02:00
Oneric
1ae09458c6 Fix redirect on logout
An instance may restrict access to the public timeline (among others)
to authenticated users and there already is a setting to decide which page
to show authenticated and unauthenticated viewers by default each.
However, the logout redirect didn't honour this setting
leading to potentially broken pages and errors on logout
2024-09-28 17:47:28 +02:00
5d22d8da49
Modernise build process 2024-09-06 07:31:42 +09:00
a78faae909
Merge remote-tracking branch 'upstream/develop' into bnakkoma 2024-09-05 15:01:33 +09:00
ef386a4d2c
Add note on readme about upstream version compatibility 2024-09-05 14:55:34 +09:00
e50997dd34
Update base docker image version 2024-09-05 14:54:26 +09:00
floatingghost
f391cf70a4 Merge pull request 'README: Add line to install Node.js' () from ilja/akkoma-fe:README_add_to_install_node into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/409
2024-08-25 09:09:35 +00:00
floatingghost
fa8fde2ab1 Merge pull request 'Upgrade vue packages' () from Oneric/akkoma-fe:vue-mathml into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/411
2024-08-25 09:08:04 +00:00
floatingghost
1f2c96a485 Merge pull request 'Fix setting restore from file' () from Oneric/akkoma-fe:fix-file-restore into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/406
2024-08-25 09:07:18 +00:00
Oneric
6c178aa257 Upgrade vue packages
Bumping past vue 3.4.0 guarantees MathML support for FEP-dc88.

Related to: https://akkoma.dev/AkkomaGang/akkoma/pulls/642
2024-08-17 18:01:59 +02:00
a41d5f021f
Merge remote-tracking branch 'upstream/develop' into bnakkoma 2024-08-15 05:58:32 +09:00
ilja
3349fe6ff2 Add line to install Node.js
For someone who isn't used to building fe things like this, it's
not always clear to install Node.js or what version.

A line has been added to the installation instructions pointing to
resources where to get it and what version to use. For version I
point to the woodpecker config because that is what the CI uses and
therefor always "tested".

There was a file .node-version who contained a node version, but
this was seriously outdated and removing it didn't seem to break
anything. Assuming it indeed doesn't do anything any more, it seems
better to remove to avoid confusion.
2024-08-03 09:54:14 +02:00
RiedleroD
94ed0991bc reverted 2e83ccefdc and clarified that compact user info is only used with enough room 2024-07-06 14:54:24 +02:00
Oneric
e274adf47d Fix setting restore from file
Previously restoring from file would also restore the old version value
breaking upload of the new settings to the server.

Additionallly it didn’t even attempt to sync settings after restore and
was insufferably slow due to individually updating every single setting
with a dispatch. Instead only update changed settings like on server
syncs which usually speeds the process up considerably.

Fixes: https://akkoma.dev/AkkomaGang/akkoma-fe/issues/405
2024-07-06 01:59:42 +02:00
RiedleroD
e955eb4503 oops, unfucked username placement 2024-07-03 18:58:50 +02:00
RiedleroD
c39d9fa64b fixed stuff overflowing in user popup e.g. in notifs 2024-07-03 18:30:51 +02:00
RiedleroD
a74a631793 stopped user handle from overflowing from its boundaries in user card 2024-07-03 17:45:40 +02:00
RiedleroD
2e83ccefdc disabled "compact user info" setting in mobile layout 2024-07-03 17:35:13 +02:00
RiedleroD
cf11b2523e disabled compact user card in mobile layout 2024-07-03 17:26:09 +02:00
Floatingghost
8765491399 Do not try to destructure when we don't need to 2024-06-27 02:58:52 +01:00
RiedleroD
85001814a2 added setting for user info compactness 2024-06-26 18:09:13 +02:00
RiedleroD
c902219997 added setting to switch between center and left-aligned user bio 2024-06-26 17:20:50 +02:00
floatingghost
4211e05a75 Merge pull request 'status component: Fix repeater name overflowing' () from yukijoou/akkoma-fe:fix-status-usernames into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/383
2024-06-25 21:34:15 +00:00
Floatingghost
a3251a1ba6 Merge remote-tracking branch 'origin/translations' into develop 2024-06-23 03:03:40 +01:00
Floatingghost
e5608f4009 remove ora as a dep 2024-06-23 03:03:11 +01:00
Floatingghost
1092d43802 remove nonsense dep 2024-06-23 03:02:45 +01:00
Weblate
98a3622172 Merge branch 'origin/develop' into Weblate. 2024-06-17 21:40:59 +00:00
floatingghost
24b9e350e2 Merge pull request 'added minimum space to empty timeline' () from Riedler/akkoma-fe:empty-tl-minspace into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/400
2024-06-17 21:40:56 +00:00
Weblate
7ab4d22a4c Merge branch 'origin/develop' into Weblate. 2024-06-17 21:40:29 +00:00
floatingghost
8489f6d5ae Merge pull request 'visually fuse CW line and post body textarea' () from Riedler/akkoma-fe:fuse-cw-to-post-body into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/401
2024-06-17 21:40:26 +00:00
RiedleroD
754cd2fa57 slightly adjusted edit button spacing 2024-06-16 17:15:04 +02:00
RiedleroD
31055fb4f2 removed min-width statements that were messing up my layouts 2024-06-16 17:14:59 +02:00
RiedleroD
918b0e3770 stopped username from wrapping… 2024-06-16 17:14:14 +02:00
RiedleroD
88aae1706a oops, removed unneeded spacing 2024-06-16 17:14:08 +02:00
RiedleroD
3d2a8a3ca2 left-aligned bio text
why the fuck was it centered in the first place?!?
2024-06-16 17:14:03 +02:00
RiedleroD
a24fff5d5b moved user stats to between user info and user actions 2024-06-16 17:14:00 +02:00
RiedleroD
b2cab6d088 only flatten top of post body textarea if subject line is visible 2024-06-16 16:26:44 +02:00
RiedleroD
3ebaba6fa7 smushed subject line and post body together, kinda 2024-06-16 16:14:16 +02:00
RiedleroD
f1058567b9 also set min height for other lists (e.g. follow requests), not just the timeline 2024-06-16 16:12:15 +02:00
RiedleroD
49a850a1e9 added minimum space to empty timeline 2024-06-16 15:49:52 +02:00
Weblate
b2de68239f Merge branch 'origin/develop' into Weblate. 2024-06-15 12:41:26 +00:00
floatingghost
c68595345f Merge pull request 'ANNOYING dependency update' () from dep-update into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/397
2024-06-15 12:41:23 +00:00
Floatingghost
a5d4b0a68c Merge branch 'develop' into dep-update 2024-06-15 13:38:40 +01:00
Floatingghost
bd263587b2 Merge branch 'develop' into dep-update 2024-06-15 13:36:24 +01:00
Weblate
c19fb198ca Merge branch 'origin/develop' into Weblate. 2024-06-15 12:33:42 +00:00
floatingghost
97966045cb Merge pull request 'make status form selectors justified properly' () from rat/akkoma-fe:status-form-selector into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/398
2024-06-15 12:33:38 +00:00
3754fa1e86
Check the loading state before allowing a search retry 2024-06-14 14:28:55 +09:00
745b8b0c7b
Update ja_pedantic translations 2024-06-14 14:14:45 +09:00
c889e88e45
Merge remote-tracking branch 'upstream/develop' into bnakkoma 2024-06-14 13:44:36 +09:00
rat
aad023c8a0 make status form selectors justified properly 2024-06-10 17:20:51 -07:00
Floatingghost
c952b2335c correct eslint plugin 2024-05-29 04:04:56 +01:00
Floatingghost
0baf31f498 correct package.json lint task 2024-05-29 04:01:29 +01:00
Floatingghost
8fa24d0c40 migrate to eslint 9 syntax 2024-05-29 03:59:37 +01:00
Floatingghost
5848c18ec8 remove unused CI file 2024-05-29 03:43:41 +01:00
Weblate
54dbead22c Merge branch 'origin/develop' into Weblate. 2024-05-28 21:33:19 +00:00
floatingghost
3e86db24d3 Update .woodpecker.yml 2024-05-28 21:33:15 +00:00
Weblate
d8f3f5002f Merge branch 'origin/develop' into Weblate. 2024-05-28 21:31:51 +00:00
floatingghost
7789c5def6 Update .woodpecker.yml 2024-05-28 21:31:46 +00:00
Floatingghost
a45f482c79 remove a useless log 2024-05-28 04:18:32 +01:00
Weblate
ed22c480f9 Merge branch 'origin/develop' into Weblate. 2024-05-28 03:17:19 +00:00
Floatingghost
f3934afbd8 make sure we normalise interfaceLanguage 2024-05-28 04:17:04 +01:00
Weblate
3797495e53 Merge branch 'origin/develop' into Weblate. 2024-05-28 03:15:47 +00:00
Floatingghost
0b437ab6fd remove line left over in conflict 2024-05-28 04:15:35 +01:00
Floatingghost
a7dea2f70f ANNOYING dependency update 2024-05-28 04:02:17 +01:00
Weblate
2c9da4a58c Merge branch 'origin/develop' into Weblate. 2024-05-28 02:25:05 +00:00
floatingghost
8964dce609 Merge pull request 'Make animated emojis in reactions pause' () from sarayalth/akkoma-fe:pause-animated-reaction into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/378
2024-05-28 02:25:02 +00:00
Weblate
156b036caa Merge branch 'origin/develop' into Weblate. 2024-05-28 02:24:09 +00:00
floatingghost
1a49a1b3ac Merge pull request 'Expand Unicode emoji map' () from Oneric/akkoma-fe:emoji_update into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/385
2024-05-28 02:24:06 +00:00
Weblate
61d82a2a07 Merge branch 'origin/develop' into Weblate. 2024-05-28 02:22:11 +00:00
Floatingghost
1adef56603 Merge remote-tracking branch 'partizan/386-default-post-lang' into develop 2024-05-28 03:22:03 +01:00
Weblate
b9bf0f0002 Merge branch 'origin/develop' into Weblate. 2024-05-28 02:14:58 +00:00
Floatingghost
5aaa605df0 add asdf tool file 2024-05-28 03:14:50 +01:00
Weblate
7136ea80b9 Merge branch 'origin/develop' into Weblate. 2024-05-28 02:14:48 +00:00
Floatingghost
71e287d56c update CI to v2 2024-05-28 03:14:37 +01:00
Weblate
a64cdda725 Merge branch 'origin/develop' into Weblate. 2024-05-28 02:10:18 +00:00
floatingghost
70275684bf Merge pull request 'Fix posting for "special" interface languages' () from Oneric/akkoma-fe:post-language-specialcodes into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/377
2024-05-28 02:10:15 +00:00
Weblate
7e7f03aece Merge branch 'origin/develop' into Weblate. 2024-05-28 02:07:18 +00:00
floatingghost
29ff2ef455 Merge pull request 'Fix ordering of favourites timeline' () from Oneric/akkoma-fe:favourite-ordering into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/392
2024-05-28 02:07:15 +00:00
Yuki Joou
8c49474dea status component: Fix repeater name overflowing
If someone repeating a post had a long username, their username would
overflow beyond the bounds of the post.

This fixes this isse by turning the bar displaying the username and
repeat icon into a flexbox.
2024-05-18 01:25:15 +02:00
Oneric
62e0dd858c Fix ordering of favourites timeline
The backend returns them order by when the post was favourited;
reordering them by post date jumbles everything up each addition
and serves no purpose.

Fixes: https://akkoma.dev/AkkomaGang/akkoma-fe/issues/391
2024-05-15 18:47:47 +02:00
sarah
cc709394c5 remove unused classes on notifications 2024-05-14 18:09:21 +02:00
Serhii Tereshchenko
57beea6a0d fix: Use label and key for options 2024-05-13 16:13:58 +03:00
105da507d7
Add search retry functionality
This commit implements query invalidation logic that runs when the
requested query is same as the last one. This allows for seamless
retries of previous searches without reloading or moving around routes,
improving user experience and reducing bandwidth usage.

Note that this commit does not change the behavior of the search bar
located in the app header.
2024-05-09 14:15:45 +09:00
Weblate
45524552a0 Translated using Weblate (Vietnamese)
Currently translated at 91.9% (964 of 1048 strings)

Translated using Weblate (Vietnamese)

Currently translated at 92.2% (965 of 1046 strings)

Translated using Weblate (Vietnamese)

Currently translated at 92.2% (965 of 1046 strings)

Translated using Weblate (Vietnamese)

Currently translated at 84.3% (882 of 1046 strings)

Translated using Weblate (Vietnamese)

Currently translated at 84.3% (882 of 1046 strings)

Translated using Weblate (Vietnamese)

Currently translated at 79.8% (835 of 1046 strings)

Translated using Weblate (Vietnamese)

Currently translated at 79.8% (835 of 1046 strings)

Co-authored-by: Nguyễn Gia Phong <cnx@loang.net>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: xarvos <huyngo@disroot.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/vi/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
ee66b69ab5 Translated using Weblate (Japanese)
Currently translated at 0.2% (3 of 1046 strings)

Added translation using Weblate (Japanese)

Co-authored-by: Nakaya <s_fpfb_sub-second@yahoo.co.jp>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
d42e374704 Translated using Weblate (Greek)
Currently translated at 15.9% (167 of 1046 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: getimiskon <getimiskon@disroot.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/el/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
ce8a9d2b4a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1048 of 1048 strings)

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1046 of 1046 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/zh_Hans/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
d2b7ac6d8c Translated using Weblate (Polish)
Currently translated at 99.7% (1045 of 1048 strings)

Translated using Weblate (Polish)

Currently translated at 99.7% (1045 of 1048 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (1046 of 1046 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (1046 of 1046 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: subtype <subtype@hollow.capital>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/pl/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
754c72cb24 Translated using Weblate (Portuguese)
Currently translated at 100.0% (1048 of 1048 strings)

Co-authored-by: Jammer Lammer <akHarINlMYExpSmVPDRT@proton.me>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/pt/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
f5bd195422 Translated using Weblate (Russian)
Currently translated at 68.7% (719 of 1046 strings)

Co-authored-by: Mel <hi@mel.gg>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ru/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
d49fd46554 Translated using Weblate (Japanese (ja_EASY))
Currently translated at 72.3% (757 of 1046 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: kazari <6c577a54-aac9-482a-955e-745c858445e3@simplelogin.com>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_EASY/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
9982373853 Translated using Weblate (Italian)
Currently translated at 80.4% (841 of 1045 strings)

Translated using Weblate (Italian)

Currently translated at 65.3% (683 of 1045 strings)

Co-authored-by: Cuche <cuche@mailbox.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/it/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
5206b5cf9c Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (1048 of 1048 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 99.2% (1040 of 1048 strings)

Co-authored-by: Toot <toothpicker@users.noreply.translate.akkoma.dev>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/zh_Hant/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
a65a06ca04 Translated using Weblate (Catalan)
Currently translated at 100.0% (1048 of 1048 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: fadelkon <fadelkon@posteo.net>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ca/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
c10b38afbc Translated using Weblate (Indonesian)
Currently translated at 71.9% (753 of 1046 strings)

Co-authored-by: Aldiantoro Nugroho <kriwil@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/id/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
Weblate
009941ea2c Translated using Weblate (Spanish)
Currently translated at 93.7% (983 of 1048 strings)

Translated using Weblate (Spanish)

Currently translated at 93.9% (983 of 1046 strings)

Translated using Weblate (Spanish)

Currently translated at 92.5% (967 of 1045 strings)

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: taretka <info@tarteka.net>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/es/
Translation: Pleroma fe/pleroma-fe
2024-04-29 14:09:37 +00:00
76b44d3a39
Fixed "Load more" functionality on search page
The previous implementation caused the "Load more" button not to load
additional search results when changes were made to the input field
2024-04-21 05:49:22 +09:00
b2d30e5c34
Fixed search type prioritization issue
Fixed the condition to prevent confusion between automatically selected
tabs and those chosen by the user.
2024-04-21 04:59:16 +09:00
Serhii Tereshchenko
042e8c78dc feat: Add "Default post language" option
Refs 
2024-04-20 16:07:03 +03:00
Oneric
0e07d88afa Expand Unicode emoji map
This pulls in 267 new emoji:
  - all 258 non-deprecated country or macro region
    flags (composed by two regional indicators)
  - all 3 regional flags currently recommended for general use
    (Wales, Scotland, England)
  - a few random ones i picked out
    - goose
    - heart on fire
    - heart mending
    - transgender flag
    - rainbow flag
    - pirate flag

The new names are derived from official Unicode names
with minor modifications to fit into the usual shortcode scheme
and dropping the flag_ prefix from country indicators.
Due to a naming conflict the old "japan" emoji
U+1F5FE SILHOUETTE OF JAPAN was renamed to "japan_silhouette".
2024-04-04 21:52:33 +02:00
60d0c91eb9
Merge remote-tracking branch 'upstream/develop' into bnakkoma 2024-03-04 07:13:14 +09:00
2a0a66eb45
Update ja_pedantic translations 2024-03-04 07:11:00 +09:00
1fe59ab9e4
Adjust default logo 2024-03-04 06:56:18 +09:00
sarah
1f5f8665c8 make animated reactions pause unless hovered on notifications 2024-03-01 20:02:19 +01:00
Oneric
428ed70b0d Fix posting for special interface languages
Easy Japanses (ja_easy) and traditional Chinses (zh_Hant) use
(custom) non-ISO codes in the interface. Because MastoAPI only accepts
ISO 639 codes, the backend will return an error rendering users
unable to do anything unless the post’s language was explicitly set.
2024-02-26 08:05:55 +01:00
floatingghost
ed0b403c33 Merge pull request 'Auto-approve followbacks (frontend part)' () from Oneric/akkoma-fe:followbacks-fe into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/365
2024-02-20 16:24:37 +00:00
floatingghost
0f842b300b Merge pull request 'Display profile background of other users' () from Oneric/akkoma-fe:profile-backgrounds into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/371
2024-02-20 16:20:14 +00:00
floatingghost
865cb6f96a Merge pull request 'Add Indonesian translation' () from leap123/akkoma-fe:leap123-patch-1 into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/366
2024-02-19 14:04:34 +00:00
Oneric
050c7df2e6 Display profile background of other users
And add a new frontend setting to hide other people's background.
2024-02-14 18:44:57 +01:00
Oneric
a77a9e04d9 Expose new server-side permit_followback setting
Added to backend in https://akkoma.dev/AkkomaGang/akkoma/pulls/674
2024-02-04 22:19:14 +01:00
d51c12f717
Merge remote-tracking branch 'upstream/develop' into bnakkoma 2024-01-29 01:24:31 +09:00
leap123
a57334991e Add Indonesian translation
The Indonesian translation is technically almost complete, just not added to messages.js
2024-01-19 04:27:26 +00:00
floatingghost
8dce31d0ad Merge pull request 'Improve UX of subject / Content Warning field' () from hazelnoot/akkoma-fe:develop into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/362
2023-12-20 18:49:40 +00:00
Hazel Koehler
ea9ad4d600 fix "always show content warning" setting 2023-12-20 12:39:31 -05:00
Hazel Koehler
34e2800f59 add button to toggle the spoiler / CW field 2023-12-16 14:44:26 -05:00
Hazel Koehler
3d65eccf04 use main emoji button for spoiler / CW field 2023-12-16 13:37:59 -05:00
floatingghost
d304be654f Merge pull request 'Update build setup instructions' () from norm/pleroma-fe:update-build-setup into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/343
2023-12-15 12:24:33 +00:00
floatingghost
aee97fa948 Merge pull request 'Re-added extension checking for still-image' () from Mergan/pleroma-fe:still-image-ultimate into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/346
2023-12-15 12:24:07 +00:00
floatingghost
7da1687f31 Merge pull request 'Use relative font size and set appropriate overflow behavior' () from xarvos/pleroma-fe:update-css into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/355
2023-12-15 12:12:28 +00:00
floatingghost
a8f193d4bd Merge pull request 'Stop constant movement of notifications due to changing timestamps' () from Oneric/akkoma-fe:notification-writhing into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/353
2023-12-15 11:57:47 +00:00
floatingghost
81c82e11bc Merge pull request 'Explicitly set SameSite attribute for cookies' () from Oneric/akkoma-fe:cookie-samesite into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/352
2023-12-15 11:54:15 +00:00
floatingghost
00cadce5b4 Merge pull request 'Format dates, times with window.navigator.language instead of UI i18n locale' () from smitten/akkoma-fe:date-locale-fix-cherrypick into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/354
2023-12-15 11:52:59 +00:00
6dee7708bc Adjust default logo 2023-11-29 03:59:13 +09:00
794b765861 Update README 2023-11-22 09:01:45 +09:00
floatingghost
40a08f279b Merge pull request 'Drop broken "@ symbol as icon" setting' () from Oneric/akkoma-fe:at-icon into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/359
2023-11-16 10:41:17 +00:00
Oneric
c524a47e6f Drop broken "@ symbol as icon" setting
It was merged into pleroma-fe on 2022-02-03 in
76547fe66d and imported
into akkoma-fe on 2022-06-08 with the merge commit
f6cf509a04.

However, something went wrong in the merge and while the setting
and its infrastructure exist, it is never used anywhere and @ is
always displayed as text.

Given it existed in this broken state for nearly one and a half years,
never worked on akkoma-fe and no bugs were filed about this, it appears
nobody cares, so let’s just remove it.
2023-11-15 23:36:19 +01:00
d5c74cccbd Disable search hotkey when modal is open 2023-11-16 06:25:14 +09:00
92aa58356b Fix race condition in search 2023-11-16 06:25:00 +09:00
c578846b98 Support for searching local posts/users only 2023-11-15 08:43:53 +09:00
8398670530 Allow the search bar to be focused with '/' and closed with 'esc' 2023-11-10 06:35:49 +09:00
315d306eb4 Show loading circle on following searches too 2023-11-10 06:35:27 +09:00
e8b653737c Improved search experience
fixed invalid variable calls
changed to manage loading state tab by tab
made automatic tab selection less aggressive
made the api call fail-safe
fixed the format of the search result count
2023-11-09 07:12:43 +09:00
bdc1e9d9c6 If refactoring is so good, why isn't there refactoring 2? 2023-11-06 08:27:14 +09:00
3afad74579 Refactor search component 2023-11-06 07:44:00 +09:00
71b0ee672e Remove the loading state as soon as possible
i meant false, not true
2023-11-06 07:33:18 +09:00
88d9ee9904 Update readme 2023-11-06 05:52:18 +09:00
5d924d0a16 Add users from search results to the global store
fixes user cards
2023-11-06 05:37:08 +09:00
3551289a86 Restyle hashtag search results with touch devices in mind 2023-11-06 05:34:00 +09:00
ca5f98aeee Search the focused search type first 2023-11-06 03:55:44 +09:00
Ngô Ngọc Đức Huy
235c734d37
Use overflow: auto for description
Previously it sets overflow: scroll, so there's an unnecessary
horizontal scroll.
overflow: auto only shows scrollbar when it overflows
2023-11-05 09:32:19 +07:00
Ngô Ngọc Đức Huy
deaef1d0b9
Use relative unit for font size 2023-11-05 09:21:01 +07:00
b808c84b55 Fix spell errors 2023-11-04 05:21:43 +09:00
42934f4299 Show search results asynchronously 2023-11-03 06:34:11 +09:00
8b63ade65b Update ja_pedantic translations 2023-11-03 06:16:51 +09:00
b35b8aeb01 Render search results using the Conversation component 2023-11-03 05:47:27 +09:00
a88fe43f03 Avoid direct access to properties 2023-11-03 05:26:51 +09:00
f54248e133 Format code 2023-11-03 05:26:04 +09:00
smitten
1b28ec3b72
Match UI i18n configuration to browser locales 2023-11-01 23:10:57 -04:00
smitten
c9dc8f00f9
Use window.navigator.language before interface i18n language 2023-10-30 23:56:53 -04:00
Oneric
beee99e733 Stop notifications boxes from change size over time
Notifications about favourites and follows use .notification-right,
notifications about replies instead use .heading-right.

Previously only the former set a min-width, however the
chosen value of 3em was too small to fit the worst case.
As a consequence, when the timestamp text changes over time,
its element width changes, which may result in neighbouring text
(no longer) needing to wrap to a new line in turn changing the size
of the whole notification box pushing older notification boxes down/up.

These constant movements at the side of the screen can be quite
annoying and confusing when the cause cannot be immediately discerned.

Avoid this, by reserving enough space for any timestamp.

For English, the worst case is the five-character 'XXmin', since the
short identifier for minutes is the longest with three letters.
With two exceptions, all other current localisation also do not exceed
three letters in any short unit identifier up to days.
However, some localisations (e.g. Polish) additionally insert a space
between numerical value and unit. This matches SI recommendations
pushing the worst case to 6 characters.

6 characters will be sufficient for timestamps up to 3 weeks in all
languages (minus prev exceptions), which seems reasonable enough
as beyond this timestamps rarely change anyway.

The aforementioned exceptions being Vietnamese and Occitan,
but in the current localisation all or the relevant short unit
identifiers are identical to the long forms indicating this is
just due to incomplete translation.
Indeed, Vietnamese Wikipedia (read through machine translation) suggests
“ph” is commonly used as unit identifiers for minutes, but the current
localisation fully spells it out as “phút”.
2023-10-25 00:37:09 +02:00
Oneric
ccb0ffdc8a Don't show direction in notification timestamps
Currently all notifications except follow-related once include
and explicit direction text. (It missing in follow notifs is due to an
omission in 804ba0cdb6b353e0c959c68f44c6a1316c0d6b10 which only added
the newly introduced with-direction to status-related notifs. Before,
presumably all notifs included direction text.)

But in the notification tray horizontal space is scarce
and notifs can already be assumed to only come from the past.
While it might not be too bad for the English localisation’s 4-letter
' ago' suffix, e.g. the Indonesian localisation’s ' yang lalu' needs
10 letters.

Thus instead of fixing the omission for follow-related notifs,
drop direction text from all notification timestamps.
2023-10-24 23:28:45 +02:00
05fd58ec3f Merge remote-tracking branch 'upstream/develop' into bnakkoma 2023-10-23 04:26:49 +09:00
c85bdb5e54 Support OpenSearch protocol 2023-10-23 04:23:48 +09:00
e1dcfaa415 Hide search filter button from unregistered users 2023-10-23 04:20:22 +09:00
2677c6f846 Fix misplaced timeline menu/filter popover layer 2023-10-23 04:14:15 +09:00
99feef1027 Update ja_pedantic translations 2023-10-23 04:13:46 +09:00
e970b3fecd Make text-overflow work on timeline menu 2023-10-22 05:16:56 +09:00
Oneric
ab250c2f3a Explicitly set SameSite attribute for cookies
Modern browsers start to tighten down on third-party access to cookies.
E.g. in current Firefox, a warning about the userLanguage cookie was
shown since it did not yet explicitly set the SameSite attribute and the
default is about to change.

The cookie name being referred to as BACKEND_LANGUAGE_COOKIE_NAME
suggests it should be readable by the actual Akkoma backend, which can
live at a different domain than akkoma-fe. Thus explicitly enable
sharing with third-party sites.

No warnings were shown for other cookies, so I assume
this was the only one not yet setting SameSite.
2023-10-19 01:05:59 +02:00
e2f1870ee0 Fix timeline header overflow and line break
fixed by changing the class to the intended one
2023-10-17 13:51:55 +09:00
Norm
1de62fffcd
Update config.example.json link and example domain 2023-10-06 04:52:04 -04:00
Norm
306cea04a1
Use corepack in build instructions 2023-10-06 04:51:59 -04:00
Mergan
d9e1bc4d99 Re-added extension checking for still-image
- Bonus refactoring
2023-10-02 15:29:54 -07:00
FloatingGhost
52b0b6f008 add VI to messages.js 2023-10-02 13:28:23 +01:00
70ab970b42 Expand user fields on click 2023-09-28 06:23:36 +09:00
37a6f2be7b Shorten version name
'akkoma 3.10.0-akkoma+bnakkoma' is unnecessarily long
2023-09-28 06:20:04 +09:00
9d92a93892 Merge remote-tracking branch 'upstream/develop' into bnakkoma 2023-09-27 06:45:50 +09:00
179a21b71e Update README 2023-09-27 05:30:00 +09:00
e01dce03d8 Remove dead code 2023-09-27 05:28:29 +09:00
d53fc6923f Support the 'following' option on post searches 2023-09-27 05:23:24 +09:00
b3ab3be3be Check the availability of media search using instance flags 2023-09-27 04:31:13 +09:00
c14dfc1581 Improve support for favicon and PWA icons 2023-09-26 08:56:32 +09:00
8c0d8bb285 Give this fork a cool name
i don't want to complicate things, so the name of the software remains akkoma,
and i will, of course, continue to follow upstream changes as much as possible

p.s. michiru kagemori is peak character design
2023-09-26 08:50:20 +09:00
floatingghost
8afbe5e3bc Merge pull request 'Making still-image better' () from Mergan/pleroma-fe:still-image-ultimate into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/341
2023-09-25 13:29:29 +00:00
floatingghost
58be48d164 Merge pull request 'Do not copy all emojis in recentEmoji getter' () from sn0w/akkoma-fe:feature/optimize-recent-emojis into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/340
2023-09-25 13:24:12 +00:00
b4d3d28f1a Update ja_pedantic translations 2023-09-24 07:15:57 +09:00
a3b03a55f0 Update ja_pedantic translations 2023-09-24 06:59:22 +09:00
9820945335 Make line wraps in checkboxes okay 2023-09-24 04:27:54 +09:00
1015801074 Fix unintentionally layered buttons 2023-09-21 05:48:40 +09:00
b7508ed23e Normalise button padding 2023-09-20 06:09:21 +09:00
3e51f18ee0 Revert "Give subscribe buttons a fixed width"
This reverts commit a4994fd879.
2023-09-20 06:03:10 +09:00
16e597d7cc Improve the a11y of the default theme 2023-09-19 07:29:03 +09:00
271c976e54 Show hints about remote users 2023-09-19 06:47:23 +09:00
1dfcad3685 Update ja_pedantic translations 2023-09-19 06:45:43 +09:00
a15f365599 Upgrade FontAwesome packages 2023-09-19 06:04:29 +09:00
a4915a6501 Please don't switch tabs left and right when searching 2023-09-17 05:50:09 +09:00
7042d0f4b6 Lazy load attachment images by default 2023-09-17 05:48:04 +09:00
139d43eda5 Track statuses from media searches too
feat. the file I forgot to stage before committing 4306c9f
2023-09-16 08:54:42 +09:00
22476d5368 Update README 2023-09-16 08:17:09 +09:00
4306c9ff6c Add support for searching media status
w/ some refactors

i've also tried to maintain support for backends that
don't have media status search support
2023-09-16 07:24:33 +09:00
20cf8f978b Allow changing NODE_ENV 2023-09-16 07:15:14 +09:00
894b89e459 Update ja_pedantic translations 2023-09-16 07:14:07 +09:00
Mergan
1056b89fd1 Disabled aggressive matching for reduced motion (we search for gif now) 2023-09-12 04:32:01 -07:00
Mergan
3e64d78d05 An oopsie 2023-09-12 04:17:28 -07:00
Mergan
3947aafeba Aligning canvas to image 2023-09-12 04:08:47 -07:00
Mergan
345934c2f3 Make label visible on avatar 2023-09-12 03:37:05 -07:00
Mergan
42a13b0f1b Modify label 2023-09-12 03:35:58 -07:00
Mergan
e13c4b6b85 Revamped still-image 2023-09-12 02:48:53 -07:00
80d5555804 Update ja_pedantic translations 2023-09-11 07:54:21 +09:00
8fd3a468bb Update ja_pedantic translations 2023-09-09 06:00:03 +09:00
da630f9255 Make the sizing of navbar buttons consistent 2023-09-09 05:44:06 +09:00
5e9a98439d You may not need flexbox everywhere 2023-09-09 05:39:26 +09:00
a4994fd879 Give subscribe buttons a fixed width 2023-09-09 05:17:22 +09:00
55fe9a51d6 Precise emoji sizing 2023-09-09 05:11:10 +09:00
56359fcccb Update changes in the readme 2023-09-07 06:46:38 +09:00
ac3598116f Make emoji dimensions consistent 2023-09-07 06:28:00 +09:00
792644344c Make user names in basic user cards bold 2023-09-06 07:22:11 +09:00
5ada7e4aaf Make user names in user cards bold 2023-09-06 07:15:25 +09:00
6c5e253f5c Improve the readability of user cards 2023-09-06 07:13:16 +09:00
59067b3429 Improve the a11y of the default theme 2023-09-06 06:25:01 +09:00
e6a0ab68a5 Adjust the position of the search icon 2023-09-06 06:16:23 +09:00
sn0w
6a1409e09b
Do not copy all emojis in recentEmoji getter 2023-09-03 16:19:06 +02:00
d43c278d9d Update ja_pedantic translations 2023-08-31 06:25:15 +09:00
e600e3b3ce Adjust search button dimensions 2023-08-31 06:24:45 +09:00
d9b7a2db7f Remove border radius from nav panel child elements 2023-08-31 06:23:25 +09:00
013e9f4061 Adjust spacing 2023-08-28 09:10:17 +09:00
78e9bf8cad Reset search button style 2023-08-28 06:39:06 +09:00
3085c08b64 Fix emoji width in Safari 2023-08-28 04:56:25 +09:00
085eb56eed Update ja_pedantic translations 2023-08-20 06:27:05 +09:00
f6d2e63455 Update ja_pedantic translations 2023-08-16 06:40:59 +09:00
d672d80447 Merge branch 'develop' into itepechi 2023-08-13 04:07:03 +09:00
b339df3b99 Update ja_pedantic translations 2023-08-13 04:02:39 +09:00
a2d838537d Update ja_pedantic translations 2023-08-12 07:42:38 +09:00
bd80244514 Update ja_pedantic translations 2023-08-12 07:14:16 +09:00
f967e79c66 Move user interaction buttons to the right 2023-08-12 07:00:27 +09:00
4dbc351e97 Update ja_pedantic translations 2023-08-12 06:41:11 +09:00
e078cf9863 Get the default post language from the instance metadata 2023-08-12 06:13:31 +09:00
d619c2f451 Align search icon on list creation page 2023-08-12 04:23:42 +09:00
8bfe88e7d9 Update ja_pedantic translations 2023-08-11 07:24:25 +09:00
fd4b204ce5 Show + after the count if more pages are available 2023-08-11 06:09:09 +09:00
Tusooa Zhu
bd3f71b030 Make search say No more results when there are current results 2023-08-11 05:07:36 +09:00
Ekaterina Vaartis
f3254129e1 Ensure uniqueness of found statuses & ensure only one loading circle 2023-08-11 04:52:21 +09:00
Ekaterina Vaartis
2ae33191ac Amend status search results, and introduce searchType
Use searchType to only search for statuses when searching for more results
2023-08-11 04:52:20 +09:00
Ekaterina Vaartis
45f3bf1848 Implement loading more statuses when searching 2023-08-11 04:46:53 +09:00
3406911d1e Clicking on the backdrop will work on all kind of modals 2023-08-10 04:53:29 +09:00
ed73c6e5cf Preserve status language properties 2023-08-09 06:57:51 +09:00
571df1fb1f Allow configuration of default post language 2023-08-08 05:39:34 +09:00
eef9314bcf Update default theme colors 2023-08-07 04:07:42 +09:00
654f46f2ca Match search component styles to the search bar 2023-08-06 19:41:39 +09:00
6108c6dff7 Center the content of alerts 2023-08-06 19:37:34 +09:00
a66fe016a0 Fix alert padding 2023-08-06 07:08:25 +09:00
503bab9151 Make search bar button inset 2023-08-06 07:00:37 +09:00
8e0227d350 Adjust spacing 2023-08-06 07:00:25 +09:00
a0d05af90d Make statuses' id part a flex container 2023-08-06 03:17:12 +09:00
b6235ac47f Adjust the spacings of alerts 2023-08-06 03:14:09 +09:00
30f3a14c30 Make events respect IM composition 2023-08-04 20:25:07 +09:00
025acda827 Note the amount of memory required 2023-08-04 20:12:44 +09:00
0180c7e4a8 Set zip compression level to 9 2023-08-04 20:12:26 +09:00
7a585a261a Revert "Make events respect IM composition"
This reverts commit a725d8fbc3.
2023-08-04 20:11:50 +09:00
648ce3dda1 Apply eslint fix 2023-08-04 19:36:08 +09:00
a725d8fbc3 Make events respect IM composition 2023-08-04 18:16:46 +09:00
83308564ef Add a Makefile for easy building 2023-08-01 23:41:41 +09:00
867c7f9993 Add an explanation of the repository 2023-08-01 20:52:24 +09:00
0c52d182f7 Reduce the radius of tooltips 2023-08-01 04:23:00 +09:00
1e160d4bb4 Fix theme identifier in default config 2023-08-01 04:20:25 +09:00
098028fff3 Update website theme 2023-07-31 04:36:06 +09:00
189 changed files with 10194 additions and 9087 deletions
.dockerignore.eslintignore.eslintrc.js.gitignore.node-version.tool-versions.woodpecker.ymlDockerfileMakefileREADME.md
build
config
eslint.config.jsindex.htmlpackage.json
src
App.jsApp.scss
boot
components
about
account_actions
attachment
avatar_list
basic_user_card
checkbox
color_input
confirm_modal
conversation
desktop_nav
domain_mute_card
edit_status_modal
emoji_input
emoji_reactions
extra_buttons
favorite_button
features_panel
follow_request_card
font_control
gallery
hashtag_link
instance_specific_panel
list
local_bubble_panel
login_form
media_modal
media_upload
mention_link
mentions_line
mfa_form
mod_modal
modal
moderation_tools
nav_panel
notification
notifications
opacity_input
pinch_zoom
poll
popover
post_status_form
post_status_modal
react_button
remote_follow
retweet_button
search
search_bar
selectable_list
settings_modal

13
.dockerignore Normal file
View file

@ -0,0 +1,13 @@
.DS_Store
node_modules/
dist/
npm-debug.log
test/unit/coverage
test/e2e/reports
selenium-debug.log
.idea/
config/local.json
config/local.*.json
docs/site/
.vscode/
akkoma-fe.zip

View file

@ -1,2 +0,0 @@
build/*.js
config/*.js

View file

@ -1,30 +0,0 @@
module.exports = {
root: true,
parserOptions: {
parser: '@babel/eslint-parser',
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: [
'plugin:vue/recommended'
],
// required to lint *.vue files
plugins: [
'vue',
'import'
],
// add your custom rules here
rules: {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'vue/require-prop-types': 0,
'vue/no-unused-vars': 0,
'no-tabs': 0,
'vue/multi-word-component-names': 0,
'vue/no-reserved-component-names': 0
}
}

3
.gitignore vendored
View file

@ -9,4 +9,5 @@ selenium-debug.log
config/local.json
config/local.*.json
docs/site/
.vscode/
.vscode/
akkoma-fe.zip

View file

@ -1 +0,0 @@
7.2.1

1
.tool-versions Normal file
View file

@ -0,0 +1 @@
nodejs 20.12.2

View file

@ -1,24 +1,25 @@
platform: linux/amd64
pipeline:
labels:
platform: linux/amd64
steps:
lint:
when:
event:
- pull_request
image: node:18
image: node:20
commands:
- yarn
- yarn lint
#- yarn stylelint
test:
when:
event:
- pull_request
image: node:18
image: node:20
commands:
- apt update
- apt install firefox-esr -y --no-install-recommends
- yarn
- yarn
- yarn unit
build:
@ -28,7 +29,7 @@ pipeline:
branch:
- develop
- stable
image: node:18
image: node:20
commands:
- yarn
- yarn build
@ -40,15 +41,15 @@ pipeline:
branch:
- develop
- stable
image: node:18
image: node:20
secrets:
- SCW_ACCESS_KEY
- SCW_SECRET_KEY
- SCW_DEFAULT_ORGANIZATION_ID
commands:
- apt-get update && apt-get install -y rclone wget zip
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.30.0/scaleway-cli_2.30.0_linux_amd64
- mv scaleway-cli_2.30.0_linux_amd64 scaleway-cli
- chmod +x scaleway-cli
- ./scaleway-cli object config install type=rclone
- zip akkoma-fe.zip -r dist
@ -70,8 +71,8 @@ pipeline:
- SCW_DEFAULT_ORGANIZATION_ID
commands:
- apt-get update && apt-get install -y rclone wget git zip
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.30.0/scaleway-cli_2.30.0_linux_amd64
- mv scaleway-cli_2.30.0_linux_amd64 scaleway-cli
- chmod +x scaleway-cli
- ./scaleway-cli object config install type=rclone
- cd docs
@ -79,4 +80,4 @@ pipeline:
- mkdocs build
- zip -r docs.zip site/*
- cd site
- rclone copy . scaleway:akkoma-docs/frontend/$CI_COMMIT_BRANCH/
- rclone copy . scaleway:akkoma-docs/frontend/$CI_COMMIT_BRANCH/

24
Dockerfile Normal file
View file

@ -0,0 +1,24 @@
FROM node:22.6.0-alpine3.20 as build
RUN apk add --no-cache \
git \
chromium-chromedriver
# use chromedriver from apk
ENV CHROMEDRIVER_FILEPATH=/usr/bin/chromedriver
WORKDIR /app/
ARG NODE_ENV=production
ENV YARN_CACHE_FOLDER=/.yarn/
COPY . /app/
RUN \
--mount=type=cache,target=/.yarn \
NODE_ENV=development yarn install \
&& NODE_ENV=${NODE_ENV} yarn run build
FROM scratch as result
COPY --from=build /app/dist/ /

26
Makefile Normal file
View file

@ -0,0 +1,26 @@
RUNTIME ?= docker
BUILD_DIR ?= ./dist/
OUTFILE_ZIP ?= ./akkoma-fe.zip
NODE_ENV ?= production
.PHONY: all
all: build-fe package
.PHONY: build-fe
build-fe:
ifeq ("$(wildcard $(BUILD_DIR))","")
mkdir $(BUILD_DIR)
else
rm -rf $(BUILD_DIR)
mkdir $(BUILD_DIR)
endif
$(RUNTIME) build --build-arg NODE_ENV=$(NODE_ENV) --output type=local,dest=./dist/ .
.PHONY: package
package:
ifneq ("$(wildcard $(OUTFILE_ZIP))","")
rm $(OUTFILE_ZIP)
endif
zip -r -9 $(OUTFILE_ZIP) $(BUILD_DIR)
# vim:set noexpandtab:

View file

@ -1,28 +1,76 @@
# Akkoma-FE
# BNAkkoma: Brand New Akkoma
![English OK](https://img.shields.io/badge/English-OK-blueviolet) ![日本語OK](https://img.shields.io/badge/%E6%97%A5%E6%9C%AC%E8%AA%9E-OK-blueviolet)
<small>It's not that new. This is just a cheap pun on the title of a [furry anime](https://en.wikipedia.org/wiki/BNA:_Brand_New_Animal).</small>
Also keep in mind that B in BNAkkoma stands for 'bleeding-edge', **features can be added, changed or removed at any time!**
If you are experiencing any strange quirks, make sure both your frontend and backend are up to date.
If your software are up to date but the bug is still there, ping me on the fediverse at '@itepechi@fedi.itepechi.me'.
## What is this?
This is a fork of [AkkomaGang/akkoma-fe](https://akkoma.dev/AkkomaGang/akkoma-fe/), with a custom theme and Makefile.
The differences from the upstream repository are described below:
- Added a Makefile where you can build the client inside a Docker container
- Refactored some components and styles
- Fixed a lot of broken CSS rules and misaligned elements
- Added and changed the default theme to a custom one
- Added support for setting the default post language
- Stole some commits from the original Pleroma frontend
- Added OpenSearch protocol support
- Added the Media tab to the search page (requires appropriate backend)
- Added 'Limit to Following' filter to the search page (requires appropriate backend)
- Added 'Limit to Local' filter to the search page (requires appropriate backend)
- Implemented lazy loading of search results
- More than 200 (!) Japanese translations have been fixed
- Removed some themes to save network bandwidth
- Improved PWA support, sort of
Although this frontend application is designed to work with the BNAkkoma backend, it remains compatible with the original version of Akkoma by only enabling additional features when the backend server returns a corresponding flag.
### How to build
**Requires 2GB+ memory.**
```sh
# Docker
make
# Podman
make RUNTIME=podman
```
---
## Akkoma-FE
![English OK](https://img.shields.io/badge/English-OK-blueviolet?style=for-the-badge) ![日本語OK](https://img.shields.io/badge/%E6%97%A5%E6%9C%AC%E8%AA%9E-OK-blueviolet?style=for-the-badge)
This is a fork of Akkoma-FE from the Pleroma project, with support for new Akkoma features such as:
- MFM support via [marked-mfm](https://akkoma.dev/sfr/marked-mfm)
- Custom emoji reactions
# For Translators
## For Translators
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Akkoma-FE.
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Akkoma-FE.
Alternatively, edit/create `src/i18n/$LANGUAGE_CODE.json` (where `$LANGUAGE_CODE` is the [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language), then add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js) if it doesn't already exist there.
Akkoma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
# FOR ADMINS
## FOR ADMINS
To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/administration/CLI_tasks/frontend/) CLI task to install Akkoma-FE, then modify your configuration as described in the [Frontend Management](https://docs.akkoma.dev/stable/configuration/frontend_management/) doc.
## Build Setup
### Build Setup
``` bash
```bash
# install dependencies
npm install -g yarn
corepack enable
yarn
# serve with hot reload at localhost:8080
@ -35,21 +83,21 @@ npm run build
npm run unit
```
# For Contributors:
## For Contributors:
You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/config/local.example.json)) to enable some convenience dev options:
You can create file `/config/local.json` (see [example](https://akkoma.dev/AkkomaGang/akkoma-fe/src/branch/develop/config/local.example.json)) to enable some convenience dev options:
* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases.
* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
- `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases.
- `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
FE Build process also leaves current commit hash in global variable `___pleromafe_commit_hash` so that you can easily see which pleroma-fe commit instance is running, also helps pinpointing which commit was used when FE was bundled into BE.
# Configuration
## Configuration
Edit config.json for configuration.
## Options
### Options
### Login methods
#### Login methods
```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.
`loginMethod` can be set to either `password` (the default) or `token`, which will use the full oauth redirection flow, which is useful for SSO situations.

View file

@ -1,36 +1,36 @@
// https://github.com/shelljs/shelljs
require('./check-versions')()
require('shelljs/global')
env.NODE_ENV = 'production'
require("./check-versions")();
require("shelljs/global");
env.NODE_ENV = "production";
var path = require('path')
var config = require('../config')
var ora = require('ora')
var webpack = require('webpack')
var webpackConfig = require('./webpack.prod.conf')
var path = require("path");
var config = require("../config");
var webpack = require("webpack");
var webpackConfig = require("./webpack.prod.conf");
console.log(
' Tip:\n' +
' Built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
)
" Tip:\n" +
" Built files are meant to be served over an HTTP server.\n" +
" Opening index.html over file:// won't work.\n",
);
var spinner = ora('building for production...')
spinner.start()
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
rm('-rf', assetsPath)
mkdir('-p', assetsPath)
cp('-R', 'static/*', assetsPath)
var assetsPath = path.join(
config.build.assetsRoot,
config.build.assetsSubDirectory,
);
rm("-rf", assetsPath);
mkdir("-p", assetsPath);
cp("-R", "static/*", assetsPath);
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n')
})
if (err) throw err;
process.stdout.write(
stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
}) + "\n",
);
});

View file

@ -5,7 +5,7 @@ var path = require('path')
var express = require('express')
var webpack = require('webpack')
var opn = require('opn')
var proxyMiddleware = require('http-proxy-middleware')
const { createProxyMiddleware } = require('http-proxy-middleware');
var webpackConfig = process.env.NODE_ENV === 'testing'
? require('./webpack.prod.conf')
: require('./webpack.dev.conf')
@ -36,7 +36,13 @@ Object.keys(proxyTable).forEach(function (context) {
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(context, options))
const targetUrl = new URL(options.target);
// add path
targetUrl.pathname = context;
options.target = targetUrl.toString();
console.log("Proxying", context, "to", options.target);
app.use(context, createProxyMiddleware(options))
})
// handle fallback for HTML5 history API

View file

@ -3,6 +3,7 @@ var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var { VueLoaderPlugin } = require('vue-loader')
const ESLintPlugin = require('eslint-webpack-plugin');
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
@ -35,6 +36,7 @@ module.exports = {
],
fallback: {
"url": require.resolve("url/"),
querystring: require.resolve("querystring-es3")
},
alias: {
'static': path.resolve(__dirname, '../static'),
@ -47,20 +49,6 @@ module.exports = {
module: {
noParse: /node_modules\/localforage\/dist\/localforage.js/,
rules: [
{
enforce: 'pre',
test: /\.(js|vue)$/,
include: projectRoot,
exclude: /node_modules/,
use: {
loader: 'eslint-loader',
options: {
formatter: require('eslint-friendly-formatter'),
sourceMap: config.build.productionSourceMap,
extract: true
}
}
},
{
enforce: 'post',
test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
@ -118,6 +106,9 @@ module.exports = {
]
},
plugins: [
new VueLoaderPlugin()
new VueLoaderPlugin(),
new ESLintPlugin({
configType: 'flat'
})
]
}

View file

@ -1,4 +1,4 @@
{
"target": "https://pleroma.soykaf.com/",
"target": "https://otp.akkoma.dev/",
"staticConfigPreference": false
}

View file

@ -2,5 +2,4 @@ var { merge } = require('webpack-merge')
var devEnv = require('./dev.env')
module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})

31
eslint.config.js Normal file
View file

@ -0,0 +1,31 @@
const pluginVue = require('eslint-plugin-vue')
const pluginImport = require('eslint-plugin-import')
module.exports = [
...pluginVue.configs['flat/recommended'],
{
languageOptions: {
parserOptions: {
parser: '@babel/eslint-parser',
sourceType: 'module'
}
},
rules: {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'vue/require-prop-types': 0,
'vue/no-unused-vars': 0,
'no-tabs': 0,
'vue/multi-word-component-names': 0,
'vue/no-reserved-component-names': 0
},
ignores: [
'build/*.js',
'config/*.js'
]
}
]

View file

@ -11,6 +11,7 @@
<link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
<!--server-generated-meta-->
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
<link rel="manifest" href="/manifest.json">
</head>
<body class="hidden">

View file

@ -1,7 +1,7 @@
{
"name": "pleroma_fe",
"version": "3.10.0",
"description": "A frontend for Akkoma instances",
"version": "3.10.0+bnakkoma",
"description": "An extended frontend for Akkoma instances",
"author": "Roger Braun <roger@rogerbraun.net>",
"private": true,
"scripts": {
@ -12,120 +12,118 @@
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"stylelint": "stylelint src/**/*.scss",
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
"lint": "eslint src test/unit/specs test/e2e/specs",
"lint-fix": "eslint --fix src test/unit/specs test/e2e/specs"
},
"dependencies": {
"@babel/runtime": "7.17.8",
"@chenfengyuan/vue-qrcode": "2.0.0",
"@chenfengyuan/vue-qrcode": "^2.0.0",
"@floatingghost/pinch-zoom-element": "^1.3.1",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1",
"@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0",
"blurhash": "^2.0.4",
"body-scroll-lock": "2.7.1",
"chromatism": "3.0.0",
"click-outside-vue3": "4.0.1",
"cropperjs": "1.5.12",
"diff": "3.5.0",
"escape-html": "1.0.3",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"blurhash": "^2.0.5",
"body-scroll-lock": "^3.1.5",
"chromatism": "^3.0.0",
"click-outside-vue3": "^4.0.1",
"cropperjs": "^1.6.2",
"diff": "^5.2.0",
"escape-html": "^1.0.3",
"iso-639-1": "^2.1.15",
"js-cookie": "^3.0.1",
"localforage": "1.10.0",
"localforage": "^1.10.0",
"parse-link-header": "^2.0.0",
"phoenix": "1.6.2",
"punycode.js": "2.1.0",
"qrcode": "1",
"url": "^0.11.0",
"vue": "^3.2.31",
"vue-i18n": "^9.2.2",
"vue-router": "4.0.14",
"vue-template-compiler": "2.6.11",
"vuex": "4.0.2"
"phoenix": "^1.7.12",
"punycode.js": "^2.3.1",
"qrcode": "^1.5.3",
"querystring-es3": "^0.2.1",
"url": "^0.11.3",
"vue": "^3.4.38",
"vue-i18n": "^9.14.0",
"vue-router": "^4.4.3",
"vue-template-compiler": "^2.7.16",
"vuex": "^4.1.0"
},
"devDependencies": {
"@babel/core": "7.17.8",
"@babel/core": "^7.24.6",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-transform-runtime": "7.17.0",
"@babel/preset-env": "7.16.11",
"@babel/register": "7.17.7",
"@babel/plugin-transform-runtime": "^7.24.6",
"@babel/preset-env": "^7.24.6",
"@babel/register": "^7.24.6",
"@intlify/vue-i18n-loader": "^5.0.0",
"@ungap/event-target": "0.2.3",
"@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
"@vue/babel-plugin-jsx": "1.1.1",
"@ungap/event-target": "^0.2.4",
"@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
"@vue/babel-plugin-jsx": "^1.2.2",
"@vue/compiler-sfc": "^3.1.0",
"@vue/test-utils": "^2.0.2",
"autoprefixer": "6.7.7",
"autoprefixer": "^10.4.19",
"babel-loader": "^9.1.0",
"babel-plugin-lodash": "3.3.4",
"babel-plugin-lodash": "^3.3.4",
"chai": "^4.3.7",
"chalk": "1.1.3",
"chromedriver": "^107.0.3",
"chalk": "^1.1.3",
"chromedriver": "^119.0.1",
"connect-history-api-fallback": "^2.0.0",
"cross-spawn": "^7.0.3",
"css-loader": "^6.7.2",
"css-loader": "^7.1.2",
"custom-event-polyfill": "^1.0.7",
"eslint": "^7.32.0",
"eslint-config-standard": "^17.0.0",
"eslint": "^9.3.0",
"eslint-config-standard": "^17.1.0",
"eslint-friendly-formatter": "^4.0.1",
"eslint-loader": "^4.0.2",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-promise": "^6.2.0",
"eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^9.7.0",
"eventsource-polyfill": "0.9.6",
"express": "4.17.3",
"eslint-plugin-vue": "^9.26.0",
"eslint-webpack-plugin": "^4.2.0",
"eventsource-polyfill": "^0.9.6",
"express": "^4.19.2",
"file-loader": "^6.2.0",
"function-bind": "1.1.1",
"function-bind": "^1.1.2",
"html-webpack-plugin": "^5.5.0",
"http-proxy-middleware": "0.21.0",
"inject-loader": "2.0.1",
"isparta-loader": "2.0.0",
"json-loader": "0.5.7",
"karma": "6.3.17",
"karma-coverage": "1.1.2",
"karma-firefox-launcher": "1.3.0",
"karma-mocha": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-sinon-chai": "2.0.2",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.33",
"http-proxy-middleware": "^3.0.0",
"json-loader": "^0.5.7",
"karma": "^6.4.3",
"karma-coverage": "^2.2.1",
"karma-firefox-launcher": "^2.1.3",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-sinon-chai": "^2.0.2",
"karma-sourcemap-loader": "^0.4.0",
"karma-spec-reporter": "^0.0.36",
"karma-webpack": "^5.0.0",
"lodash": "4.17.21",
"lolex": "1.6.0",
"mini-css-extract-plugin": "0.12.0",
"mocha": "3.5.3",
"nightwatch": "0.9.21",
"opn": "4.0.2",
"ora": "0.4.1",
"lodash": "^4.17.21",
"lolex": "^6.0.0",
"mini-css-extract-plugin": "^2.9.0",
"mocha": "^10.4.0",
"nightwatch": "^3.6.3",
"opn": "^6.0.0",
"postcss-html": "^1.5.0",
"postcss-loader": "3.0.0",
"postcss-loader": "^8.1.1",
"postcss-sass": "^0.5.0",
"raw-loader": "0.5.1",
"sass": "^1.56.0",
"sass-loader": "^13.2.0",
"selenium-server": "2.53.1",
"semver": "5.7.1",
"shelljs": "0.8.5",
"sinon": "2.4.1",
"sinon-chai": "2.14.0",
"raw-loader": "^4.0.2",
"sass": "^1.77.2",
"sass-loader": "^14.2.1",
"selenium-server": "^3.141.59",
"semver": "^7.6.2",
"shelljs": "^0.8.5",
"sinon": "^18.0.0",
"sinon-chai": "^3.7.0",
"stylelint": "^14.15.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^29.0.0",
"stylelint-config-standard-scss": "^6.1.0",
"stylelint-rscss": "^0.4.0",
"url-loader": "^4.1.1",
"vue-loader": "^17.0.0",
"vue-style-loader": "^4.1.2",
"webpack": "^5.75.0",
"webpack-dev-middleware": "^5.3.3",
"webpack-hot-middleware": "^2.25.1",
"webpack-merge": "^5.8.0",
"workbox-webpack-plugin": "^6.5.4"
"vue-loader": "^17.4.2",
"vue-style-loader": "^4.1.3",
"webpack": "^5.91.0",
"webpack-dev-middleware": "^7.2.1",
"webpack-hot-middleware": "^2.26.1",
"webpack-merge": "^5.10.0",
"workbox-webpack-plugin": "^7.1.0"
},
"engines": {
"node": ">= 16.0.0",

View file

@ -64,6 +64,11 @@ export default {
'-' + this.layoutType
]
},
pageBackground () {
return this.mergedConfig.displayPageBackgrounds
? this.$store.state.users.displayBackground
: null
},
currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image },
instanceBackground () {
@ -71,7 +76,7 @@ export default {
? null
: this.$store.state.instance.background
},
background () { return this.userBackground || this.instanceBackground },
background () { return this.pageBackground || this.userBackground || this.instanceBackground },
bgStyle () {
if (this.background) {
return {

View file

@ -8,7 +8,7 @@
}
html {
font-size: 14px;
font-size: 0.875rem;
// overflow-x: clip causes my browser's tab to crash with SIGILL lul
}
@ -665,8 +665,12 @@ option {
}
.alert {
margin: 0 0.35em;
padding: 0 0.25em;
display: inline-flex;
justify-content: center;
align-items: center;
line-height: 1;
margin: 0 0.5em;
padding: 0.35em;
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
@ -767,6 +771,8 @@ option {
.btn.button-default {
min-height: 2em;
padding-left: 0.5em;
padding-right: 0.5em;
}
.new-status-notification {
@ -780,6 +786,11 @@ option {
.mobile-hidden {
display: none;
}
.btn.button-default {
padding-left: 0.7em;
padding-right: 0.7em;
}
}
@keyframes spin {

View file

@ -77,7 +77,9 @@ const getInstanceConfig = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
// don't override cookie if set
if (!Cookies.get('userLanguage')) {
store.dispatch('setOption', { name: 'interfaceLanguage', value: resolveLanguage(data.languages) })
const language = resolveLanguage(data.languages)
store.dispatch('setOption', { name: 'interfaceLanguage', value: language })
store.dispatch('setOption', { name: 'postLanguage', value: language })
}
if (vapidPublicKey) {
@ -183,6 +185,12 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('renderMisskeyMarkdown')
copyInstanceOption('sidebarRight')
if (config.backendCommitUrl)
copyInstanceOption('backendCommitUrl')
if (config.frontendCommitUrl)
copyInstanceOption('frontendCommitUrl')
return store.dispatch('setTheme', config['theme'])
}
@ -279,6 +287,10 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'translationEnabled', value: features.includes('akkoma:machine_translation') })
store.dispatch('setInstanceOption', { name: 'searchTypeMediaEnabled', value: features.includes('bnakkoma:search_type_media') })
store.dispatch('setInstanceOption', { name: 'searchOptionFollowingEnabled', value: features.includes('bnakkoma:search_option_following') })
store.dispatch('setInstanceOption', { name: 'searchOptionLocalEnabled', value: features.includes('bnakkoma:search_option_local') })
store.dispatch('setInstanceOption', { name: 'opensearchProtocolSupported', value: features.includes('bnakkoma:opensearch_protocol') })
const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@ -402,6 +414,16 @@ const afterStoreSetup = async ({ store, i18n }) => {
getTOS({ store })
getStickers({ store })
// Create a link tag for OpenSearch and forget about it
if (store.state.instance.opensearchProtocolSupported) {
const node = document.createElement('link')
node.setAttribute('rel', 'search')
node.setAttribute('type', 'application/opensearchdescription+xml')
node.setAttribute('href', '/opensearch.xml')
node.setAttribute('title', store.state.instance.name)
document.head.appendChild(node)
}
const router = createRouter({
history: createWebHistory(),
routes: routes(store),

View file

@ -9,7 +9,7 @@
</div>
</template>
<script src="./about.js" ></script>
<script src="./about.js"></script>
<style lang="scss">
</style>

View file

@ -6,7 +6,7 @@
:bound-to="{ x: 'container' }"
remove-padding
>
<template v-slot:content>
<template #content>
<div class="dropdown-menu">
<template v-if="relationship.following">
<button
@ -71,7 +71,7 @@
</button>
</div>
</template>
<template v-slot:trigger>
<template #trigger>
<button class="button-unstyled ellipsis-button">
<FAIcon
class="icon"
@ -93,7 +93,7 @@
keypath="user_card.block_confirm"
tag="span"
>
<template v-slot:user>
<template #user>
<span
v-text="user.screen_name_ui"
/>

View file

@ -37,7 +37,7 @@
white-space: pre-line;
word-break: break-word;
text-overflow: ellipsis;
overflow: scroll;
overflow: auto;
}
&.-static {

View file

@ -155,6 +155,7 @@
>
<StillImage
class="image"
:loading="'lazy'"
:referrerpolicy="referrerpolicy"
:mimetype="attachment.mimetype"
:src="attachment.large_thumb_url || attachment.url"
@ -246,8 +247,8 @@
ref="flash"
class="flash"
:src="attachment.large_thumb_url || attachment.url"
@playerOpened="setFlashLoaded(true)"
@playerClosed="setFlashLoaded(false)"
@player-opened="setFlashLoaded(true)"
@player-closed="setFlashLoaded(false)"
/>
</span>
</div>

View file

@ -14,7 +14,7 @@
</div>
</template>
<script src="./avatar_list.js" ></script>
<script src="./avatar_list.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -61,12 +61,11 @@
}
&-user-name {
img {
object-fit: contain;
height: 16px;
width: 16px;
vertical-align: middle;
}
--emoji-size: 14px;
}
&-user-name-value {
font-weight: bold;
}
&-user-name-value,

View file

@ -22,31 +22,36 @@
<script>
export default {
emits: ['update:modelValue'],
props: [
'modelValue',
'indeterminate',
'disabled'
]
],
emits: ['update:modelValue']
}
</script>
<style lang="scss">
@import '../../_variables.scss';
$checkbox-size: 1.2em;
$padding-size: 0.5em;
.checkbox {
position: relative;
display: inline-block;
min-height: 1.2em;
min-height: $checkbox-size;
padding-left: calc($checkbox-size + $padding-size);
&-indicator {
position: relative;
padding-left: 1.2em;
padding-left: calc($checkbox-size + $padding-size);
margin-left: calc(($checkbox-size + $padding-size) * -1);
}
&-indicator::before {
position: absolute;
right: 0;
left: 0;
top: 0;
display: block;
content: '✓';
@ -92,11 +97,6 @@ export default {
color: $fallback--text;
color: var(--inputText, $fallback--text);
}
}
& > span {
margin-left: .5em;
}
}
</style>

View file

@ -14,7 +14,7 @@
:model-value="present"
:disabled="disabled"
class="opt"
@update:modelValue="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
@update:model-value="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
/>
<div class="input color-input-field">
<input
@ -46,7 +46,6 @@
</div>
</div>
</template>
<style lang="scss" src="./color_input.scss"></style>
<script>
import Checkbox from '../checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
@ -108,6 +107,7 @@ export default {
}
}
</script>
<style lang="scss" src="./color_input.scss"></style>
<style lang="scss">
.color-control {

View file

@ -25,6 +25,8 @@
</dialog-modal>
</template>
<script src="./confirm_modal.js"></script>
<style lang="scss" scoped>
@import '../../_variables';
@ -35,5 +37,3 @@
}
}
</style>
<script src="./confirm_modal.js"></script>

View file

@ -267,11 +267,11 @@ const conversation = {
},
replies () {
let i = 1
// eslint-disable-next-line camelcase
return reduce(this.conversation, (result, { id, in_reply_to_status_id }) => {
/* eslint-disable camelcase */
const irid = in_reply_to_status_id
/* eslint-enable camelcase */
if (irid) {
result[irid] = result[irid] || []
result[irid].push({

View file

@ -91,7 +91,7 @@
:controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
@toggle-expanded="toggleExpanded"
/>
<div
v-if="showOtherRepliesButtonBelowStatus && getReplies(status.id).length > 1"
@ -184,7 +184,7 @@
:toggle-status-content-property="toggleStatusContentProperty"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
@toggle-expanded="toggleExpanded"
/>
</div>
</div>

View file

@ -51,7 +51,6 @@ export default {
ConfirmModal
},
data: () => ({
searchBarHidden: true,
supportsMask: window.CSS && window.CSS.supports && (
window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') ||
@ -77,8 +76,7 @@ export default {
},
logoBgStyle () {
return Object.assign({
'margin': `${this.$store.state.instance.logoMargin} 0`,
opacity: this.searchBarHidden ? 1 : 0
'margin': `${this.$store.state.instance.logoMargin} 0`
}, this.enableMask ? {} : {
'background-color': this.enableMask ? '' : 'transparent'
})
@ -120,9 +118,6 @@ export default {
scrollToTop () {
window.scrollTo(0, 0)
},
onSearchBarToggled (hidden) {
this.searchBarHidden = hidden
},
openSettingsModal () {
this.$store.dispatch('openSettingsModal')
},

View file

@ -56,13 +56,6 @@
.logo {
grid-area: logo;
position: relative;
transition: opacity;
transition-timing-function: ease-out;
transition-duration: 100ms;
@media all and (min-width: 800px) {
opacity: 1 !important;
}
.mask {
mask-repeat: no-repeat;

View file

@ -44,9 +44,9 @@
/>
</router-link>
<router-link
v-if="publicTimelineVisible"
:to="{ name: 'public-timeline' }"
class="nav-icon"
v-if="publicTimelineVisible"
>
<FAIcon
fixed-width
@ -68,9 +68,9 @@
/>
</router-link>
<router-link
v-if="federatedTimelineVisible"
:to="{ name: 'public-external-timeline' }"
class="nav-icon"
v-if="federatedTimelineVisible"
>
<FAIcon
fixed-width
@ -98,7 +98,6 @@
<div class="item right actions">
<search-bar
v-if="currentUser || !privateMode"
@toggled="onSearchBarToggled"
@click.stop
/>
<div

View file

@ -9,7 +9,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.unmute') }}
<template v-slot:progress>
<template #progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
@ -19,7 +19,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.mute') }}
<template v-slot:progress>
<template #progress>
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>

View file

@ -2,7 +2,7 @@
<Modal
v-if="isFormVisible"
class="edit-form-modal-view"
@backdropClicked="closeModal"
@backdrop-clicked="closeModal"
>
<div class="edit-form-modal-panel panel">
<div class="panel-heading">
@ -11,10 +11,10 @@
<PostStatusForm
class="panel-body"
v-bind="params"
@posted="closeModal"
:disablePolls="true"
:disableVisibilitySelector="true"
:disable-polls="true"
:disable-visibility-selector="true"
:post-handler="doEditStatus"
@posted="closeModal"
/>
</div>
</Modal>

View file

@ -43,7 +43,10 @@
:class="{ highlighted: index === highlighted }"
@click.stop.prevent="onClick($event, suggestion)"
>
<span v-if="!suggestion.mfm" class="image">
<span
v-if="!suggestion.mfm"
class="image"
>
<img
v-if="suggestion.img"
:src="suggestion.img"

View file

@ -122,14 +122,14 @@ export const suggestUsers = ({ dispatch, state }) => {
const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
}).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
displayText: screen_name_ui,
detailText: name,
imageUrl: profile_image_url_original,
replacement: '@' + screen_name + ' '
}))
/* eslint-enable camelcase */
suggestions = newSuggestions || []
return suggestions

View file

@ -42,7 +42,7 @@
</div>
</template>
<script src="./emoji_reactions.js" ></script>
<script src="./emoji_reactions.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -7,7 +7,7 @@
:bound-to="{ x: 'container' }"
remove-padding
>
<template v-slot:content="{close}">
<template #content="{close}">
<div class="dropdown-menu">
<button
v-if="canMute && !status.thread_muted"
@ -172,7 +172,7 @@
</button>
</div>
</template>
<template v-slot:trigger>
<template #trigger>
<button class="button-unstyled popover-trigger">
<FAIcon
class="fa-scale-110 fa-old-padding"
@ -205,7 +205,7 @@
</Popover>
</template>
<script src="./extra_buttons.js" ></script>
<script src="./extra_buttons.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -35,7 +35,7 @@
</div>
</template>
<script src="./favorite_button.js" ></script>
<script src="./favorite_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -23,7 +23,7 @@
</div>
</template>
<script src="./features_panel.js" ></script>
<script src="./features_panel.js"></script>
<style lang="scss">
.features-panel li {

View file

@ -1,5 +1,8 @@
<template>
<basic-user-card :user="user" v-if="show">
<basic-user-card
v-if="show"
:user="user"
>
<div class="follow-request-card-content-container">
<button
class="btn button-default"

View file

@ -47,7 +47,7 @@
</div>
</template>
<script src="./font_control.js" ></script>
<script src="./font_control.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -31,8 +31,8 @@
:description="descriptions && descriptions[attachment.id]"
:hide-description="size === 'small' || tooManyAttachments && hidingLong"
:style="itemStyle(attachment.id, row.items)"
@setMedia="onMedia"
@naturalSizeLoad="onNaturalSizeLoad"
@set-media="onMedia"
@natural-size-load="onNaturalSizeLoad"
/>
</div>
</div>

View file

@ -14,6 +14,6 @@
</span>
</template>
<script src="./hashtag_link.js"/>
<script src="./hashtag_link.js" />
<style lang="scss" src="./hashtag_link.scss"/>
<style lang="scss" src="./hashtag_link.scss" />

View file

@ -10,4 +10,4 @@
</div>
</template>
<script src="./instance_specific_panel.js" ></script>
<script src="./instance_specific_panel.js"></script>

View file

@ -42,6 +42,7 @@ export default {
@import '../../_variables.scss';
.list {
min-height: 1em;
&-item:not(:last-child) {
border-bottom: 1px solid;
border-bottom-color: $fallback--border;

View file

@ -10,7 +10,7 @@
</div>
</div>
<div class="panel-body">
<p>{{ $t("about.bubble_instances_description")}}:</p>
<p>{{ $t("about.bubble_instances_description") }}:</p>
<ul>
<li
v-for="instance in bubbleInstances"

View file

@ -90,7 +90,7 @@
</div>
</template>
<script src="./login_form.js" ></script>
<script src="./login_form.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -2,7 +2,7 @@
<Modal
v-if="showing"
class="media-modal-view"
@backdropClicked="hideIfNotSwiped"
@backdrop-clicked="hideIfNotSwiped"
>
<SwipeClick
v-if="type === 'image'"

View file

@ -42,7 +42,7 @@ const mediaUpload = {
.then((fileData) => {
self.$emit('uploaded', fileData)
self.decreaseUploadCount()
}, (error) => { // eslint-disable-line handle-callback-err
}, (error) => {
self.$emit('upload-failed', 'default')
self.decreaseUploadCount()
})

View file

@ -26,7 +26,7 @@
</label>
</template>
<script src="./media_upload.js" ></script>
<script src="./media_upload.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -93,9 +93,6 @@ const MentionLink = {
this.highlightType
]
},
useAtIcon () {
return this.mergedConfig.useAtIcon
},
isRemote () {
return this.userName !== this.userNameFull
},

View file

@ -66,6 +66,6 @@
</span>
</template>
<script src="./mention_link.js"/>
<script src="./mention_link.js" />
<style lang="scss" src="./mention_link.scss"/>
<style lang="scss" src="./mention_link.scss" />

View file

@ -37,5 +37,5 @@
</span>
</span>
</template>
<script src="./mentions_line.js" ></script>
<script src="./mentions_line.js"></script>
<style lang="scss" src="./mentions_line.scss" />

View file

@ -69,4 +69,4 @@
</div>
</div>
</template>
<script src="./recovery_form.js" ></script>
<script src="./recovery_form.js"></script>

View file

@ -18,6 +18,7 @@
<input
id="code"
v-model="code"
autocomplete="one-time-code"
class="form-control"
>
</div>

View file

@ -5,6 +5,7 @@
class="mod-modal"
:class="{ peek: modalPeeked }"
:no-background="modalPeeked"
@backdropClicked="closeModal"
>
<div class="mod-modal-panel panel">
<div class="panel-heading">

View file

@ -4,7 +4,7 @@
class="panel-heading"
@click="toggleHidden"
>
<h4>{{ $t('moderation.reports.report') + ' ' + this.account.screen_name }}</h4>
<h4>{{ $t('moderation.reports.report') + ' ' + account.screen_name }}</h4>
<button
v-if="isOpen"
class="button-default"
@ -24,7 +24,7 @@
class="button-default"
@click.stop="updateReportState('open')"
>
{{ $t('moderation.reports.reopen') }}
{{ $t('moderation.reports.reopen') }}
</button>
</div>
<div
@ -35,7 +35,10 @@
<div v-if="content">
{{ decode(content) }}
</div>
<i v-else class="faint">
<i
v-else
class="faint"
>
{{ $t('moderation.reports.no_content') }}
</i>
<div class="report-author">
@ -43,12 +46,12 @@
class="small-avatar"
:user="actor"
/>
{{ this.actor.screen_name }}
{{ actor.screen_name }}
</div>
</div>
<div
v-if="!hidden && statuses.length > 0"
class="dropdown"
v-if="!hidden && this.statuses.length > 0"
>
<button
class="button button-unstyled dropdown-header"
@ -74,8 +77,8 @@
</div>
</div>
<div
v-if="!hidden && notes.length > 0"
class="dropdown"
v-if="!hidden && this.notes.length > 0"
>
<button
class="button button-unstyled dropdown-header"
@ -99,9 +102,9 @@
</div>
<div class="report-add-note">
<textarea
v-model.trim="note"
rows="1"
cols="1"
v-model.trim="note"
:placeholder="$t('moderation.reports.note_placeholder')"
/>
<button
@ -134,7 +137,7 @@
:offset="{ y: 5 }"
remove-padding
>
<template v-slot:trigger>
<template #trigger>
<button
class="btn button-default"
:disabled="!tagPolicyEnabled"
@ -147,7 +150,7 @@
/>
</button>
</template>
<template v-slot:content="{close}">
<template #content="{close}">
<div
class="dropdown-menu"
:disabled="!tagPolicyEnabled"

View file

@ -6,7 +6,7 @@
class="small-avatar"
:user="user"
/>
{{ this.user.screen_name }}
{{ user.screen_name }}
</div>
<div class="header-right">
<Timeago

View file

@ -22,6 +22,9 @@ export default {
default: false
}
},
emits: [
'backdropClicked',
],
computed: {
classes () {
return {

View file

@ -8,7 +8,7 @@
@show="setToggled(true)"
@close="setToggled(false)"
>
<template v-slot:content>
<template #content>
<div class="dropdown-menu">
<span v-if="user.is_local">
<button
@ -122,7 +122,7 @@
</span>
</div>
</template>
<template v-slot:trigger>
<template #trigger>
<button
class="btn button-default btn-block moderation-tools-button"
:class="{ toggled }"
@ -137,11 +137,11 @@
v-if="showDeleteUserDialog"
:on-cancel="deleteUserDialog.bind(this, false)"
>
<template v-slot:header>
<template #header>
{{ $t('user_card.admin_menu.delete_user') }}
</template>
<p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
<template v-slot:footer>
<template #footer>
<button
class="btn button-default"
@click="deleteUserDialog(false)"

View file

@ -102,7 +102,7 @@
</div>
</template>
<script src="./nav_panel.js" ></script>
<script src="./nav_panel.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@ -125,20 +125,6 @@
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
padding: 0;
&:first-child .menu-item {
border-top-right-radius: $fallback--panelRadius;
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
border-top-left-radius: $fallback--panelRadius;
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
}
&:last-child .menu-item {
border-bottom-right-radius: $fallback--panelRadius;
border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
border-bottom-left-radius: $fallback--panelRadius;
border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius);
}
}
li:last-child {

View file

@ -6,6 +6,7 @@ import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import StillImage from '../still-image/still-image.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@ -50,7 +51,8 @@ const Notification = {
Timeago,
Status,
RichContent,
ConfirmModal
ConfirmModal,
StillImage
},
methods: {
toggleUserExpanded () {

View file

@ -116,12 +116,13 @@
scope="global"
keypath="notifications.reacted_with"
>
<img
<still-image
v-if="notification.emoji_url !== null"
class="notification-reaction-emoji"
:src="notification.emoji_url"
:name="notification.emoji"
>
:title="notification.emoji"
:alt="notification.emoji"
/>
<span
v-else
class="emoji-reaction-emoji"
@ -151,7 +152,6 @@
>
<Timeago
:time="notification.created_at"
:with-direction="true"
:auto-update="240"
/>
</router-link>

View file

@ -5,7 +5,7 @@
placement="bottom"
:bound-to="{ x: 'container' }"
>
<template v-slot:content>
<template #content>
<div class="dropdown-menu">
<button
class="button-default dropdown-item"
@ -72,7 +72,7 @@
</button>
</div>
</template>
<template v-slot:trigger>
<template #trigger>
<button class="filter-trigger-button button-unstyled">
<FAIcon icon="filter" />
</button>

View file

@ -105,9 +105,12 @@
flex: 1;
padding-left: 0.8em;
min-width: 0;
}
.heading-right, .notification-right {
.timeago {
min-width: 3em;
display: inline-block;
min-width: 6em;
text-align: right;
}
}

View file

@ -14,7 +14,7 @@
:model-value="present"
:disabled="disabled"
class="opt"
@update:modelValue="$emit('update:modelValue', !present ? fallback : undefined)"
@update:model-value="$emit('update:modelValue', !present ? fallback : undefined)"
/>
<input
:id="name"

View file

@ -2,7 +2,6 @@
<pinch-zoom
class="pinch-zoom-parent"
v-bind="$attrs"
v-on="$listeners"
>
<slot />
</pinch-zoom>

View file

@ -18,7 +18,9 @@
:placeholder="$t('polls.option')"
:maxlength="maxLength"
@change="updatePollToParent"
@keydown.enter.stop.prevent="nextOption(index)"
@keydown.enter.stop.prevent="
$event.isComposing || $event.keyCode === 229 || nextOption(index)
"
>
</div>
<button

View file

@ -3,14 +3,14 @@
@mouseenter="onMouseenter"
@mouseleave="onMouseleave"
>
<button
<div
ref="trigger"
class="button-unstyled popover-trigger-button"
type="button"
@click="onClick"
>
<slot name="trigger" />
</button>
</div>
<div
v-if="!hidden"
ref="content"

View file

@ -9,11 +9,12 @@ import StatusContent from '../status_content/status_content.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
import { reject, map, uniqBy, debounce } from 'lodash'
import { usePostLanguageOptions } from 'src/lib/post_language'
import suggestor from '../emoji_input/suggestor.js'
import { mapGetters, mapState } from 'vuex'
import Checkbox from '../checkbox/checkbox.vue'
import Select from '../select/select.vue'
import iso6391 from 'iso-639-1'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -62,6 +63,13 @@ const deleteDraft = (draftKey) => {
localStorage.setItem('drafts', JSON.stringify(draftData));
}
const interfaceToISOLanguage = (ilang) => {
const sep = ilang.indexOf("_");
return sep < 0 ?
ilang :
ilang.substr(0, sep);
}
const PostStatusForm = {
props: [
'statusId',
@ -129,6 +137,13 @@ const PostStatusForm = {
this.$refs.textarea.focus()
}
},
setup() {
const {postLanguageOptions} = usePostLanguageOptions()
return {
postLanguageOptions,
}
},
data () {
const preset = this.$route.query.message
let statusText = preset || ''
@ -138,7 +153,15 @@ const PostStatusForm = {
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
}
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, interfaceLanguage } = this.$store.getters.mergedConfig
const {
postContentType: contentType,
postLanguage: defaultPostLanguage,
sensitiveByDefault,
sensitiveIfSubject,
interfaceLanguage,
alwaysShowSubjectInput,
} = this.$store.getters.mergedConfig
const postLanguage = defaultPostLanguage || interfaceToISOLanguage(interfaceLanguage)
let statusParams = {
spoilerText: this.subject || '',
@ -149,12 +172,13 @@ const PostStatusForm = {
poll: {},
mediaDescriptions: {},
visibility: this.suggestedVisibility(),
language: interfaceLanguage,
language: postLanguage,
contentType
}
if (this.statusId || this.isRedraft) {
const statusContentType = this.statusContentType || contentType
const statusLanguage = this.statusLanguage || language
statusParams = {
spoilerText: this.subject || '',
status: this.statusText || '',
@ -164,7 +188,7 @@ const PostStatusForm = {
poll: this.statusPoll || {},
mediaDescriptions: this.statusMediaDescriptions || {},
visibility: this.statusScope || this.suggestedVisibility(),
language: this.statusLanguage || interfaceLanguage,
language: this.statusLanguage || postLanguage,
contentType: statusContentType
}
}
@ -189,8 +213,8 @@ const PostStatusForm = {
poll: draft.data.poll,
mediaDescriptions: draft.data.mediaDescriptions,
visibility: draft.data.visibility,
language: draft.data.language,
contentType: draft.data.contentType
contentType: draft.data.contentType,
language: draft.data.language
}
if (draft.data.poll) {
@ -199,6 +223,10 @@ const PostStatusForm = {
}
}
// When first loading the form, hide the subject (CW) field if it's disabled or doesn't have a starting value.
// "disableSubject" seems to take priority over "alwaysShowSubjectInput".
const showSubject = !this.disableSubject && (statusParams.spoilerText || alwaysShowSubjectInput)
return {
dropFiles: [],
uploadingFiles: false,
@ -213,7 +241,10 @@ const PostStatusForm = {
preview: null,
previewLoading: false,
emojiInputShown: false,
idempotencyKey: ''
idempotencyKey: '',
activeEmojiInput: undefined,
activeTextInput: undefined,
subjectVisible: showSubject
}
},
computed: {
@ -302,13 +333,11 @@ const PostStatusForm = {
...mapState({
mobileLayout: state => state.interface.mobileLayout
}),
isoLanguages () {
return iso6391.getAllCodes();
}
},
watch: {
'newStatus': {
deep: true,
flush: 'sync',
handler () {
this.statusChanged()
}
@ -674,8 +703,33 @@ const PostStatusForm = {
this.$refs['emoji-input'].resize()
},
showEmojiPicker () {
this.$refs['textarea'].focus()
this.$refs['emoji-input'].triggerShowPicker()
if (!this.activeEmojiInput || !this.activeTextInput)
this.focusStatusInput()
this.$refs[this.activeTextInput].focus()
this.$refs[this.activeEmojiInput].triggerShowPicker()
},
focusStatusInput() {
this.activeEmojiInput = 'emoji-input'
this.activeTextInput = 'textarea'
},
focusSubjectInput() {
this.activeEmojiInput = 'subject-emoji-input'
this.activeTextInput = 'subject-input'
},
toggleSubjectVisible() {
// If hiding CW, then we need to clear the subject and reset focus
if (this.subjectVisible)
{
this.focusStatusInput()
// "nsfw" property is normally set by the @change listener, but this bypasses it.
// We need to clear it manually instead.
this.newStatus.spoilerText = ''
this.newStatus.nsfw = false
}
this.subjectVisible = !this.subjectVisible
},
clearError () {
this.error = null

View file

@ -118,13 +118,16 @@
/>
</div>
<EmojiInput
v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
v-if="subjectVisible"
ref="subject-emoji-input"
v-model="newStatus.spoilerText"
enable-emoji-picker
hide-emoji-button
:suggest="emojiSuggestor"
class="form-control"
>
<input
ref="subject-input"
v-model="newStatus.spoilerText"
type="text"
:placeholder="$t('post_status.content_warning')"
@ -132,6 +135,7 @@
size="1"
class="form-post-subject"
@input="onSubjectInput"
@focus="focusSubjectInput()"
>
</EmojiInput>
<i18n-t
@ -166,13 +170,18 @@
cols="1"
:disabled="posting && !optimisticPosting"
class="form-post-body"
:class="{ 'scrollable-form': !!maxHeight }"
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
:class="{ 'scrollable-form': !!maxHeight, '-has-subject': subjectVisible }"
@keydown.exact.enter="
$event.isComposing ||
$event.keyCode === 229 ||
(submitOnEnter && postStatus($event, newStatus))
"
@keydown.meta.enter="postStatus($event, newStatus)"
@keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
@input="resize"
@compositionupdate="resize"
@paste="paste"
@focus="focusStatusInput()"
/>
<p
v-if="hasStatusLengthLimit"
@ -185,6 +194,7 @@
<div
v-if="!disableScopeSelector"
class="visibility-tray"
:class="{ 'visibility-tray-edit': isEdit }"
>
<scope-selector
v-if="!disableVisibilitySelector"
@ -195,47 +205,50 @@
/>
<div
class="language-selector"
>
<Select
id="post-language"
v-model="newStatus.language"
class="form-control"
>
<option
v-for="language in isoLanguages"
:key="language"
:value="language"
class="format-selector-container">
<div
class="format-selector"
>
{{ language }}
</option>
</Select>
</div>
<div
v-if="postFormats.length > 1"
class="text-format"
>
<Select
id="post-content-type"
v-model="newStatus.contentType"
class="form-control"
>
<option
v-for="postFormat in postFormats"
:key="postFormat"
:value="postFormat"
<Select
id="post-language"
v-model="newStatus.language"
class="form-control"
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
</option>
</Select>
</div>
<div
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
class="text-format"
>
<span class="only-format">
{{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
</span>
<option
v-for="language in postLanguageOptions"
:key="language.key"
:value="language.value"
>
{{ language.label }}
</option>
</Select>
</div>
<div
v-if="postFormats.length > 1"
class="text-format format-selector"
>
<Select
id="post-content-type"
v-model="newStatus.contentType"
class="form-control"
>
<option
v-for="postFormat in postFormats"
:key="postFormat"
:value="postFormat"
>
{{ $t(`post_status.content_type["${postFormat}"]`) }}
</option>
</Select>
</div>
<div
v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
class="text-format format-selector"
>
<span class="only-format">
{{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
</span>
</div>
</div>
</div>
</div>
@ -276,6 +289,15 @@
>
<FAIcon icon="poll-h" />
</button>
<button
v-if="!disableSubject"
class="spoiler-icon button-unstyled"
:class="{ selected: subjectVisible }"
:title="$t('post_status.toggle_content_warning')"
@click="toggleSubjectVisible"
>
<FAIcon icon="eye-slash" />
</button>
</div>
<button
v-if="posting"
@ -446,6 +468,10 @@
align-items: baseline;
}
.visibility-tray-edit {
justify-content: right;
}
.visibility-notice.edit-warning {
> :first-child {
margin-top: 0;
@ -456,7 +482,13 @@
}
}
.media-upload-icon, .poll-icon, .emoji-icon {
.format-selector-container {
.format-selector {
display: inline-block;
}
}
.media-upload-icon, .poll-icon, .emoji-icon, .spoiler-icon {
font-size: 1.85em;
line-height: 1.1;
flex: 1;
@ -499,6 +531,11 @@
.poll-icon {
order: 3;
justify-content: center;
}
.spoiler-icon {
order: 4;
justify-content: right;
}
@ -551,6 +588,11 @@
line-height: 1.85;
}
.form-post-subject {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.form-post-body {
// TODO: make a resizable textarea component?
box-sizing: content-box; // needed for easier computation of dynamic size
@ -563,6 +605,11 @@
min-height: calc(var(--post-line-height) * 1em);
resize: none;
&.-has-subject {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
&.scrollable-form {
overflow-y: auto;
}

View file

@ -3,7 +3,7 @@
v-if="isLoggedIn && !resettingForm"
:is-open="modalActivated"
class="post-form-modal-view"
@backdropClicked="closeModal"
@backdrop-clicked="closeModal"
>
<div class="post-form-modal-panel panel">
<div class="panel-heading">

View file

@ -8,13 +8,13 @@
remove-padding
@show="focusInput"
>
<template v-slot:content="{close}">
<template #content="{close}">
<EmojiPicker
:enable-sticker-picker="false"
@emoji="addReaction($event, close)"
/>
</template>
<template v-slot:trigger>
<template #trigger>
<button
class="button-unstyled popover-trigger"
:title="$t('tool_tip.add_reaction')"
@ -28,7 +28,7 @@
</Popover>
</template>
<script src="./react_button.js" ></script>
<script src="./react_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -2,7 +2,7 @@ export default {
props: [ 'user' ],
computed: {
subscribeUrl () {
// eslint-disable-next-line no-undef
const serverUrl = new URL(this.user.statusnet_profile_url)
return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`
}

View file

@ -54,7 +54,7 @@
</div>
</template>
<script src="./retweet_button.js" ></script>
<script src="./retweet_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';

View file

@ -1,38 +1,54 @@
import FollowCard from '../follow_card/follow_card.vue'
import Conversation from '../conversation/conversation.vue'
import Status from '../status/status.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import SearchFilters from './search_filters.vue'
import map from 'lodash/map'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCircleNotch,
faSearch
} from '@fortawesome/free-solid-svg-icons'
import { uniqBy } from 'lodash'
library.add(
faCircleNotch,
faSearch
)
const allSearchTypes = Object.freeze(['statuses', 'media', 'accounts', 'hashtags'])
const Search = {
components: {
FollowCard,
Conversation,
Status,
TabSwitcher
TabSwitcher,
SearchFilters
},
props: [
'query'
],
data () {
return {
loaded: false,
loading: false,
queryCount: 0,
loadedInitially: false,
loading: Object.fromEntries(
allSearchTypes.map((searchType) => [searchType, false])
),
searchTerm: this.query || '',
userIds: [],
statuses: [],
media: [],
hashtags: [],
currenResultTab: 'statuses'
allTabs: allSearchTypes,
currentResultTab: 'statuses',
hasUserSelectedTab: false,
statusesOffset: 0,
lastStatusFetchCount: 0,
mediaOffset: 0,
lastMediaFetchCount: 0,
lastQuery: '',
filter: {}
}
},
computed: {
@ -45,6 +61,20 @@ const Search = {
return this.statuses.filter(status =>
allStatusesObject[status.id] && !allStatusesObject[status.id].deleted
)
},
visibleMedia () {
const allStatusesObject = this.$store.state.statuses.allStatusesObject
return this.media.filter(status =>
allStatusesObject[status.id] && !allStatusesObject[status.id].deleted
)
},
canSearchMediaPosts () {
return this.$store.state.instance.searchTypeMediaEnabled === true
},
hasAtLeastOneResult () {
return allSearchTypes
.some((searchType) => this.getVisibleLength(searchType) > 0)
}
},
mounted () {
@ -54,55 +84,191 @@ const Search = {
query (newValue) {
this.searchTerm = newValue
this.search(newValue)
},
filter: {
deep: true,
handler () {
this.lastQuery = "" // invalidate state
this.search(this.searchTerm)
}
}
},
methods: {
newQuery (query) {
this.$router.push({ name: 'search', query: { query } })
this.$refs.searchInput.focus()
if (this.lastQuery === query && !this.loading[this.currentResultTab]) {
// Handle search retries
this.lastQuery = "" // invalidate state
this.search(query)
} else {
this.$router.push({ name: 'search', query: { query } })
this.$refs.searchInput.focus()
}
},
search (query) {
async search (query, searchType = null) {
if (!query) {
this.loading = false
for (const searchType of allSearchTypes) {
this.loading[searchType] = false
}
return
}
this.loading = true
this.userIds = []
this.statuses = []
this.hashtags = []
this.$refs.searchInput.blur()
const localQueryCount = ++this.queryCount
this.$store.dispatch('search', { q: query, resolve: true })
.then(data => {
this.loading = false
this.userIds = map(data.accounts, 'id')
this.statuses = data.statuses
this.hashtags = data.hashtags
this.currenResultTab = this.getActiveTab()
this.loaded = true
})
const isNewSearch = this.lastQuery !== query
this.$refs.searchInput.blur()
if (isNewSearch) {
this.userIds = []
this.hashtags = []
this.statuses = []
this.media = []
this.statusesOffset = 0
this.lastStatusFetchCount = 0
this.mediaOffset = 0
this.lastMediaFetchCount = 0
}
let searchTypes = allSearchTypes
if (searchType) {
// Search only for `searchType` if it is provided
searchTypes = [searchType]
} else if (this.hasUserSelectedTab && this.currentResultTab !== 'statuses') {
// Start the search from the tab that the user has selected
// No need to sort if userPreferredTab === 'statuses'
searchTypes = [
this.currentResultTab,
...allSearchTypes.filter((tab) => tab !== this.currentResultTab)
]
}
for (const searchType of searchTypes) {
this.loading[searchType] = true
}
let oldStatusesLength = this.statuses.length
let oldMediaLength = this.media.length
let skipMediaSearch = !this.canSearchMediaPosts
for (const searchType of searchTypes) {
try {
if (searchType === 'media' && skipMediaSearch) {
continue
}
let searchOffset
if (searchType === 'statuses') {
searchOffset = this.statusesOffset
} else if (searchType === 'media') {
searchOffset = this.mediaOffset
}
const data = await this.$store.dispatch('search', {
q: query,
resolve: true,
offset: searchOffset,
'type': searchType,
following:
'followingOnly' in this.filter && this.filter.followingOnly,
local: 'localOnly' in this.filter && this.filter.localOnly
})
if (localQueryCount !== this.queryCount) {
// Query count differs, there should be a newer query
return
}
// Always append to old results. If new results are empty, this doesn't change anything
this.userIds = this.userIds.concat(map(data.accounts, 'id'))
this.statuses = uniqBy(this.statuses.concat(data.statuses), 'id')
if ('media' in data) {
this.media = uniqBy(this.media.concat(data.media), 'id')
}
this.hashtags = this.hashtags.concat(data.hashtags)
if (isNewSearch) {
if (!this.hasUserSelectedTab) {
this.currentResultTab = this.getFirstTabWithResults()
}
if (searchType === 'statuses' && data.statuses.length === 0) {
// Safe to assume that there are no media posts
skipMediaSearch = true
}
}
} catch (error) {
console.error(error)
} finally {
if (localQueryCount !== this.queryCount) {
// Skip cleanups if there's a newer query
return
}
if (!this.loadedInitially && this.hasAtLeastOneResult) {
// Show results on the first meaningful response
this.loadedInitially = true
}
this.loading[searchType] = false
if (searchType === 'statuses') {
// Offset from whatever we already have
this.statusesOffset = this.statuses.length
// Because the amount of new statuses can actually be zero, compare to old length instead
this.lastStatusFetchCount = this.statuses.length - oldStatusesLength
} else if (searchType === 'media') {
this.mediaOffset = this.media.length
this.lastMediaFetchCount = this.media.length - oldMediaLength
}
}
}
this.lastQuery = query
this.loadedInitially = true
for (const searchType of allSearchTypes) {
this.loading[searchType] = false
}
},
resultCount (tabName) {
const length = this[tabName].length
return length === 0 ? '' : ` (${length})`
resultCount (tab) {
const length = this.getVisibleLength(tab)
if (length === 0 || !this.loadedInitially) {
return ''
}
if (
(tab === 'statuses' && this.lastStatusFetchCount !== 0) ||
(tab === 'media' && this.lastMediaFetchCount !== 0)
) {
return ` (${length}+)`
}
return ` (${length})`
},
onResultTabSwitch (key) {
this.currenResultTab = key
this.currentResultTab = key
this.hasUserSelectedTab = true
},
getActiveTab () {
if (this.visibleStatuses.length > 0) {
return 'statuses'
} else if (this.users.length > 0) {
return 'people'
} else if (this.hashtags.length > 0) {
return 'hashtags'
getFirstTabWithResults () {
for (const tab of allSearchTypes) {
if (this.getVisibleLength(tab) > 0) {
return tab
}
}
return 'statuses'
},
lastHistoryRecord (hashtag) {
return hashtag.history && hashtag.history[0]
},
getVisibleLength (tab) {
if (tab === 'statuses') {
return this.visibleStatuses.length
} else if (tab === 'media') {
return this.visibleMedia.length
} else if (tab === 'accounts') {
return this.users.length
} else if (tab === 'hashtags') {
return this.hashtags.length
}
}
}
}

View file

@ -4,6 +4,9 @@
<div class="title">
{{ $t('nav.search') }}
</div>
<SearchFilters
v-model="filter"
/>
</div>
<div class="search-input-container">
<input
@ -11,7 +14,9 @@
v-model="searchTerm"
class="search-input"
:placeholder="$t('nav.search')"
@keyup.enter="newQuery(searchTerm)"
@keydown.enter="
$event.isComposing || $event.keyCode === 229 || newQuery(searchTerm)
"
>
<button
class="btn button-default search-button"
@ -21,30 +26,25 @@
<FAIcon icon="search" />
</button>
</div>
<div
v-if="loading"
class="text-center loading-icon"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
<div v-else-if="loaded">
<div v-if="loadedInitially">
<div class="search-nav-heading">
<tab-switcher
ref="tabSwitcher"
:on-switch="onResultTabSwitch"
:active-tab="currenResultTab"
:active-tab="currentResultTab"
>
<span
key="statuses"
:label="$t('user_card.statuses') + resultCount('visibleStatuses')"
:label="$t('user_card.statuses') + resultCount('statuses')"
/>
<span
key="people"
:label="$t('search.people') + resultCount('users')"
v-if="canSearchMediaPosts"
key="media"
:label="$t('user_card.media') + resultCount('media')"
/>
<span
key="accounts"
:label="$t('search.people') + resultCount('accounts')"
/>
<span
key="hashtags"
@ -54,27 +54,97 @@
</div>
</div>
<div class="panel-body">
<div v-if="currenResultTab === 'statuses'">
<div
v-if="visibleStatuses.length === 0 && !loading && loaded"
class="search-result-heading"
>
<h4>{{ $t('search.no_results') }}</h4>
</div>
<Status
v-for="status in visibleStatuses"
:key="status.id"
:collapsable="false"
:expandable="false"
:compact="false"
class="search-result"
:statusoid="status"
:no-heading="false"
<div
v-if="!Object.values(loading).includes(false) && !hasAtLeastOneResult"
class="text-center loading-icon"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
<div v-else-if="currenResultTab === 'people'">
<div v-if="currentResultTab === 'statuses'">
<Conversation
v-for="status in visibleStatuses"
:key="status.id"
:collapsable="true"
class="status-fadein"
:status-id="status.id"
/>
<button
v-if="!loading['statuses'] && loadedInitially && lastStatusFetchCount > 0"
class="more-statuses-button button-unstyled -link -fullwidth"
@click.prevent="search(lastQuery, 'statuses')"
>
<div class="new-status-notification text-center">
{{ $t('search.load_more') }}
</div>
</button>
<div
v-if="users.length === 0 && !loading && loaded"
v-else-if="loading['statuses'] && statusesOffset > 0"
class="text-center loading-icon"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
<div
v-if="
(visibleStatuses.length === 0 || lastStatusFetchCount === 0) &&
!loading['statuses'] && loadedInitially
"
class="search-result-heading"
>
<h4>
{{ visibleStatuses.length === 0 ? $t('search.no_results') : $t('search.no_more_results') }}
</h4>
</div>
</div>
<div v-if="currentResultTab === 'media'">
<Conversation
v-for="media in visibleMedia"
:key="media.id"
:collapsable="true"
class="status-fadein"
:status-id="media.id"
/>
<button
v-if="!loading['media'] && loadedInitially && lastMediaFetchCount > 0"
class="more-statuses-button button-unstyled -link -fullwidth"
@click.prevent="search(lastQuery, 'media')"
>
<div class="new-status-notification text-center">
{{ $t('search.load_more') }}
</div>
</button>
<div
v-else-if="loading['media'] && mediaOffset > 0"
class="text-center loading-icon"
>
<FAIcon
icon="circle-notch"
spin
size="lg"
/>
</div>
<div
v-if="
(visibleMedia.length === 0 || lastMediaFetchCount === 0) &&
!loading['media'] && loadedInitially
"
class="search-result-heading"
>
<h4>
{{ visibleMedia.length === 0 ? $t('search.no_results') : $t('search.no_more_results') }}
</h4>
</div>
</div>
<div v-else-if="currentResultTab === 'accounts'">
<div
v-if="users.length === 0 && !loading['accounts'] && loadedInitially"
class="search-result-heading"
>
<h4>{{ $t('search.no_results') }}</h4>
@ -86,9 +156,9 @@
class="list-item search-result"
/>
</div>
<div v-else-if="currenResultTab === 'hashtags'">
<div v-else-if="currentResultTab === 'hashtags'">
<div
v-if="hashtags.length === 0 && !loading && loaded"
v-if="hashtags.length === 0 && !loading['hashtags'] && loadedInitially"
class="search-result-heading"
>
<h4>{{ $t('search.no_results') }}</h4>
@ -147,13 +217,6 @@
}
}
.search-result {
box-sizing: border-box;
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
}
.search-result-footer {
border-width: 1px 0 0 0;
border-style: solid;
@ -178,6 +241,10 @@
.search-button {
margin-left: 0.5em;
width: 3em;
display: flex;
justify-content: center;
align-items: center;
}
}
@ -189,6 +256,12 @@
display: flex;
align-items: center;
& + & {
border-top: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
}
.hashtag {
flex: 1 1 auto;
color: $fallback--text;
@ -196,6 +269,12 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-sizing: border-box;
a {
display: inline-block;
padding: .5em 1em;
}
}
.count {
@ -210,4 +289,8 @@
}
}
.more-statuses-button {
height: 3.5em;
line-height: 3.5em;
}
</style>

View file

@ -0,0 +1,116 @@
<template>
<Popover
v-if="showFilters"
trigger="click"
class="SearchFilters"
placement="bottom"
:bound-to="{ x: 'container' }"
>
<template #content>
<div class="dropdown-menu">
<button
v-if="canSearchFollowing"
class="button-default dropdown-item"
@click="toggleFilter('followingOnly')"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': currentFilter.followingOnly }"
/>{{ $t('lists.following_only') }}
</button>
<button
v-if="canSearchLocal"
class="button-default dropdown-item"
@click="toggleFilter('localOnly')"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': currentFilter.localOnly }"
/>{{ $t('search.local_only') }}
</button>
</div>
</template>
<template #trigger>
<button class="filter-trigger-button button-unstyled">
<FAIcon icon="filter" />
</button>
</template>
</Popover>
</template>
<script>
import Popover from '../popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faFilter } from '@fortawesome/free-solid-svg-icons'
library.add(
faFilter
)
export default {
components: { Popover },
props: [
'modelValue'
],
emits: [
'update:modelValue'
],
data () {
return {
currentFilter: {
followingOnly: false,
localOnly: false
}
}
},
computed: {
showFilters () {
return this.canSearchFollowing || this.canSearchLocal
},
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
canSearchFollowing () {
return this.isLoggedIn &&
this.$store.state.instance.searchOptionFollowingEnabled
},
canSearchLocal () {
return this.$store.state.instance.searchOptionLocalEnabled
},
},
created () {
for (const filterName of Object.entries(this.currentFilter)) {
if (this.modelValue && filterName in this.modelValue) {
this.currentFilter[filterName] = this.modelValue[filterName]
}
}
},
methods: {
toggleFilter (filterName) {
if (filterName in this.currentFilter) {
this.currentFilter[filterName] = !this.currentFilter[filterName]
} else {
this.currentFilter[filterName] = true
}
this.$emit('update:modelValue', this.currentFilter)
}
}
}
</script>
<style lang="scss">
.SearchFilters {
align-self: stretch;
> button {
line-height: 100%;
height: 100%;
width: var(--__panel-heading-height-inner);
text-align: center;
svg {
font-size: 1.2em;
}
}
}
</style>

View file

@ -10,6 +10,12 @@ library.add(
)
const SearchBar = {
mounted () {
window.addEventListener('keydown', this.autoFocus)
},
beforeDestroy () {
window.removeEventListener('keydown', this.autoFocus)
},
data: () => ({
searchTerm: undefined,
hidden: true,
@ -36,6 +42,38 @@ const SearchBar = {
this.$refs.searchInput.focus()
}
})
},
autoFocus (event) {
if (
event.target.tagName !== 'BODY' ||
event.altKey ||
event.ctrlKey ||
event.isComposing ||
event.metaKey ||
event.repeat ||
event.shiftKey ||
// not very vue-esque, but as long as it works
document.querySelector('.modal-view.modal-background.open')
) {
return
}
if (event.key === '/') {
if (this.hidden) {
this.toggleHidden()
} else {
this.$refs.searchInput.focus()
}
event.preventDefault()
} else if (event.key === 'Escape') {
if (!this.hidden) {
this.toggleHidden()
}
event.preventDefault()
}
},
blur () {
this.$refs.searchInput.blur()
}
}
}

View file

@ -24,7 +24,12 @@
class="search-bar-input"
:placeholder="$t('nav.search')"
type="text"
@keyup.enter="find(searchTerm)"
@keydown.enter="
$event.isComposing || $event.keyCode === 229 || find(searchTerm)
"
@keydown.escape="
$event.isComposing || blur()
"
>
<button
class="button-default search-button"
@ -58,8 +63,7 @@
.SearchBar {
display: inline-flex;
align-items: baseline;
vertical-align: baseline;
align-items: center;
justify-content: flex-end;
&.-expanded {
@ -71,13 +75,21 @@
height: 29px;
}
.search-button {
margin-left: 0.5em;
width: 3em;
display: flex;
justify-content: center;
align-items: center;
}
.search-bar-input {
flex: 1 0 auto;
margin-left: 0.5em;
}
.cancel-search {
height: 50px;
height: 100%;
}
.cancel-icon {

View file

@ -24,7 +24,7 @@
:items="items"
:get-key="getKey"
>
<template v-slot:item="{item}">
<template #item="{item}">
<div
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
@ -41,7 +41,7 @@
/>
</div>
</template>
<template v-slot:empty>
<template #empty>
<slot name="empty" />
</template>
</List>

View file

@ -6,7 +6,7 @@
<Checkbox
:model-value="state"
:disabled="disabled"
@update:modelValue="update"
@update:model-value="update"
>
<span
v-if="!!$slots.default"

View file

@ -8,7 +8,7 @@
<Select
:model-value="state"
:disabled="disabled"
@update:modelValue="update"
@update:model-value="update"
>
<option
v-for="option in options"

View file

@ -6,14 +6,14 @@
<Popover
trigger="hover"
>
<template v-slot:trigger>
<template #trigger>
&nbsp;
<FAIcon
icon="wrench"
:aria-label="$t('settings.setting_changed')"
/>
</template>
<template v-slot:content>
<template #content>
<div class="modified-tooltip">
{{ $t('settings.setting_changed') }}
</div>

View file

@ -6,14 +6,14 @@
<Popover
trigger="hover"
>
<template v-slot:trigger>
<template #trigger>
&nbsp;
<FAIcon
icon="server"
:aria-label="$t('settings.setting_server_side')"
/>
</template>
<template v-slot:content>
<template #content>
<div class="serverside-tooltip">
{{ $t('settings.setting_server_side') }}
</div>

View file

@ -69,7 +69,7 @@ const SettingsModal = {
this.$store.dispatch('closeSettingsModal')
},
logout () {
this.$router.replace('/main/public')
this.$router.replace(this.$store.state.instance.redirectRootNoLogin || '/main/all')
this.$store.dispatch('closeSettingsModal')
this.$store.dispatch('logout')
},

View file

@ -4,6 +4,7 @@
class="settings-modal"
:class="{ peek: modalPeeked }"
:no-background="modalPeeked"
@backdropClicked="closeModal"
>
<div class="settings-modal-panel panel">
<div class="panel-heading">
@ -108,7 +109,7 @@
<Checkbox
:model-value="!!expertLevel"
class="expertMode"
@update:modelValue="expertLevel = Number($event)"
@update:model-value="expertLevel = Number($event)"
>
{{ $t("settings.expert_mode") }}
</Checkbox>

View file

@ -72,7 +72,7 @@ const DataImportExportTab = {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
// eslint-disable-next-line no-undef
return user.screen_name + '@' + location.hostname
}
return user.screen_name

View file

@ -4,12 +4,14 @@ import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
import IntegerSetting from '../helpers/integer_setting.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import { usePostLanguageOptions } from 'src/lib/post_language'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import ServerSideIndicator from '../helpers/server_side_indicator.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faGlobe, faSync
} from '@fortawesome/free-solid-svg-icons'
import iso6391 from 'iso-639-1'
library.add(
faGlobe,
@ -17,6 +19,11 @@ library.add(
)
const GeneralTab = {
setup() {
const {postLanguageOptions} = usePostLanguageOptions()
return {postLanguageOptions}
},
data () {
return {
subjectLineOptions: ['email', 'noop', 'masto'].map(mode => ({
@ -80,6 +87,15 @@ const GeneralTab = {
label: this.$t(`post_status.content_type["${format}"]`)
}))
},
postLanguages () {
return iso6391.getLanguages(iso6391.getAllCodes())
.map(lang => ({
key: lang.code,
value: lang.code,
label: lang.nativeName,
}))
.sort((a, b) => a.label.localeCompare(b.label));
},
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
instanceWallpaperUsed () {
return this.$store.state.instance.background &&
@ -118,6 +134,12 @@ const GeneralTab = {
this.$store.dispatch('setOption', { name: 'translationLanguage', value: val })
}
},
postLanguage: {
get: function () { return this.$store.getters.mergedConfig.postLanguage },
set: function (val) {
this.$store.dispatch('setOption', { name: 'postLanguage', value: val })
}
},
...SharedComputedObject()
},
methods: {

View file

@ -44,7 +44,6 @@
<template
v-if="profilesExpanded"
>
<div
v-for="profile in settingsProfiles"
:key="profile.id"
@ -73,15 +72,24 @@
</button>
</template>
</div>
<button class="btn button-default" @click="refreshProfiles()">
<button
class="btn button-default"
@click="refreshProfiles()"
>
{{ $t('settings.settings_profiles_refresh') }}
<FAIcon icon="sync" @click="refreshProfiles()" />
<FAIcon
icon="sync"
@click="refreshProfiles()"
/>
</button>
<h3>{{ $t('settings.settings_profile_creation') }}</h3>
<label for="settings-profile-new-name">
{{ $t('settings.settings_profile_creation_new_name_label') }}
</label>
<input v-model="newProfileName" id="settings-profile-new-name">
<input
id="settings-profile-new-name"
v-model="newProfileName"
>
<button
class="btn button-default"
@click="createSettingsProfile"
@ -146,6 +154,21 @@
{{ $t('settings.show_wider_shortcuts') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="displayPageBackgrounds">
{{ $t('settings.show_page_backgrounds') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="centerAlignBio">
{{ $t('settings.center_align_bio') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="compactUserInfo">
{{ $t('settings.compact_user_info') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }}
@ -483,14 +506,6 @@
</BooleanSetting>
</li>
</ul>
<li>
<BooleanSetting
path="useAtIcon"
expert="1"
>
{{ $t('settings.use_at_icon') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="mentionLinkShowAvatar">
{{ $t('settings.mention_link_show_avatar') }}
@ -579,6 +594,15 @@
{{ $t('settings.subject_line_behavior') }}
</ChoiceSetting>
</li>
<li>
<ChoiceSetting
id="postLanguage"
path="postLanguage"
:options="postLanguages"
>
{{ $t('settings.post_status_language') }}
</ChoiceSetting>
</li>
<li v-if="postFormats.length > 0">
<ChoiceSetting
id="postContentType"
@ -588,6 +612,15 @@
{{ $t('settings.post_status_content_type') }}
</ChoiceSetting>
</li>
<li>
<ChoiceSetting
id="postLanguage"
path="postLanguage"
:options="postLanguageOptions"
>
{{ $t('settings.post_language') }}
</ChoiceSetting>
</li>
<li>
<BooleanSetting
path="alwaysShowNewPostButton"

View file

@ -85,7 +85,7 @@ const MutesAndBlocks = {
// check is it's a local user
if (user && user.is_local) {
// append the instance address
// eslint-disable-next-line no-undef
return user.screen_name + '@' + location.hostname
}
return user.screen_name

View file

@ -10,7 +10,7 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_block')"
>
<template v-slot="row">
<template #default="row">
<BlockCard
:user-id="row.item"
/>
@ -21,7 +21,7 @@
:refresh="true"
:get-key="i => i"
>
<template v-slot:header="{selected}">
<template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@ -29,7 +29,7 @@
:click="() => blockUsers(selected)"
>
{{ $t('user_card.block') }}
<template v-slot:progress>
<template #progress>
{{ $t('user_card.block_progress') }}
</template>
</ProgressButton>
@ -39,16 +39,16 @@
:click="() => unblockUsers(selected)"
>
{{ $t('user_card.unblock') }}
<template v-slot:progress>
<template #progress>
{{ $t('user_card.unblock_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template v-slot:item="{item}">
<template #item="{item}">
<BlockCard :user-id="item" />
</template>
<template v-slot:empty>
<template #empty>
{{ $t('settings.no_blocks') }}
</template>
</BlockList>
@ -63,7 +63,7 @@
:query="queryUserIds"
:placeholder="$t('settings.search_user_to_mute')"
>
<template v-slot="row">
<template #default="row">
<MuteCard
:user-id="row.item"
/>
@ -74,7 +74,7 @@
:refresh="true"
:get-key="i => i"
>
<template v-slot:header="{selected}">
<template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@ -82,7 +82,7 @@
:click="() => muteUsers(selected)"
>
{{ $t('user_card.mute') }}
<template v-slot:progress>
<template #progress>
{{ $t('user_card.mute_progress') }}
</template>
</ProgressButton>
@ -92,16 +92,16 @@
:click="() => unmuteUsers(selected)"
>
{{ $t('user_card.unmute') }}
<template v-slot:progress>
<template #progress>
{{ $t('user_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template v-slot:item="{item}">
<template #item="{item}">
<MuteCard :user-id="item" />
</template>
<template v-slot:empty>
<template #empty>
{{ $t('settings.no_mutes') }}
</template>
</MuteList>
@ -114,7 +114,7 @@
:query="queryKnownDomains"
:placeholder="$t('settings.type_domains_to_mute')"
>
<template v-slot="row">
<template #default="row">
<DomainMuteCard
:domain="row.item"
/>
@ -125,7 +125,7 @@
:refresh="true"
:get-key="i => i"
>
<template v-slot:header="{selected}">
<template #header="{selected}">
<div class="bulk-actions">
<ProgressButton
v-if="selected.length > 0"
@ -133,16 +133,16 @@
:click="() => unmuteDomains(selected)"
>
{{ $t('domain_mute_card.unmute') }}
<template v-slot:progress>
<template #progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
</div>
</template>
<template v-slot:item="{item}">
<template #item="{item}">
<DomainMuteCard :domain="item" />
</template>
<template v-slot:empty>
<template #empty>
{{ $t('settings.no_mutes') }}
</template>
</DomainMuteList>

View file

@ -33,6 +33,7 @@ const ProfileTab = {
newName: this.$store.state.users.currentUser.name_unescaped,
newBio: unescape(this.$store.state.users.currentUser.description),
newLocked: this.$store.state.users.currentUser.locked,
newPermitFollowback: this.$store.state.users.currentUser.permit_followback,
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role,
@ -129,14 +130,15 @@ const ProfileTab = {
note: this.newBio,
locked: this.newLocked,
// Backend notation.
/* eslint-disable camelcase */
display_name: this.newName,
fields_attributes: this.newFields.filter(el => el != null),
bot: this.bot,
show_role: this.showRole,
status_ttl_days: this.expirePosts ? this.newPostTTLDays : -1,
permit_followback: this.permit_followback,
accepts_direct_messages_from: this.userAcceptsDirectMessagesFrom
/* eslint-enable camelcase */
}
if (this.emailLanguage) {
@ -185,7 +187,7 @@ const ProfileTab = {
})
return
}
// eslint-disable-next-line no-undef
const reader = new FileReader()
reader.onload = ({ target }) => {
const img = target.result

View file

@ -110,11 +110,9 @@
max="730"
class="expire-posts-days"
:placeholder="$t('settings.expire_posts_input_placeholder')"
/>
</p>
<p>
>
</p>
<p />
<p>
<interface-language-switcher
:prompt-text="$t('settings.email_language')"
@ -259,6 +257,19 @@
<BooleanSetting path="serverSide_locked">
{{ $t('settings.lock_account_description') }}
</BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !serverSide_locked}]"
>
<li>
<BooleanSetting
path="serverSide_permitFollowback"
:disabled="!serverSide_locked"
>
{{ $t('settings.permit_followback_description') }}
</BooleanSetting>
</li>
</ul>
</li>
<li>
<BooleanSetting path="serverSide_discoverable">

View file

@ -21,8 +21,10 @@
</div>
<div class="panel-body theme-preview-content">
<div class="post">
<div class="avatar still-image">
( ͡° ͜ʖ ͡°)
<div class="Avatar post-avatar">
<div class="still-image avatar -better-shadow">
( ͡° ͜ʖ ͡°)
</div>
</div>
<div class="content">
<h4>

View file

@ -1,22 +1,25 @@
import { extractCommit } from 'src/services/version/version.service'
const pleromaFeCommitUrl = 'https://akkoma.dev/AkkomaGang/pleroma-fe/commit/'
const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/'
function joinURL(base, subpath) {
return URL.parse(subpath, base)?.href || "invalid base URL"
}
const VersionTab = {
data () {
const instance = this.$store.state.instance
return {
backendCommitUrl: instance.backendCommitUrl,
backendVersion: instance.backendVersion,
frontendCommitUrl: instance.frontendCommitUrl,
frontendVersion: instance.frontendVersion
}
},
computed: {
frontendVersionLink () {
return pleromaFeCommitUrl + this.frontendVersion
return joinURL(this.frontendCommitUrl, this.frontendVersion)
},
backendVersionLink () {
return pleromaBeCommitUrl + extractCommit(this.backendVersion)
return joinURL(this.backendCommitUrl, extractCommit(this.backendVersion))
}
}
}

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