Compare commits

...
Sign in to create a new pull request.

333 commits

Author SHA1 Message Date
Oneric
b05f81586a Show error when fav or repeat fails
So it’s clear this is not just a FE bug with a visually stuck indicator
and to give insight into _why_ it currently doesn’t work.
2026-06-09 00:00:00 +00:00
Oneric
9d42541a2e Don't lie about fav or retweet success
If the request errored, neither the then clause refetching fav
details was executed, nor did we receive new status data with
the correct state for ourselves. This left just the "optimisticly"
hallucinated success state from the FE, making it _seem_ like
everything went through fine for the user, while in reality
their action never actually registered.
This was highly misleading.
2026-06-09 00:00:00 +00:00
Oneric
2ca80b0247 Refetch fav and repeat details only after pending change
Since the issue only occurs sporadically it’s hard to be 100% certain,
but barring backend bugs or unforseen other parts of the FE interferring
this should fix disappearing (but successfull) favs and repeats.

Fixes: AkkomaGang/akkoma-fe#370
2026-06-09 00:00:00 +00:00
Oneric
42be450340 extra_buttons/redraft: only delete after source was obtained
In unfortunate timing edge cases the deletion request could in theory
be processed first and the status get lost before its details
can populate the new post draft form.

Fixes: AkkomaGang/akkoma-fe#502
2026-05-31 00:00:00 +00:00
d0a01a30ce Merge pull request 'Collapse overly long polls and allow viewing results without voting' (#523) from Oneric/akkoma-fe:long-polls into develop
Reviewed-on: AkkomaGang/akkoma-fe#523
2026-05-31 17:21:42 +00:00
Oneric
d402121f8c poll: collapse overly long polls
There is no limit on accepted remote poll option count
and even for same option count individual texts can be quite long.

Flex items seem to deal with margins differently than the previous
display type. Before the margins were overlapping, now margins are
separate. Thus the vertical poll-option margin needed to be halved
to preserve the visual appearance.

Unlike for posts, the collapse button is placed at the top
to make it easier to collapse it back for ridiculous lengths
where scrolling all the way down takes too much effort.
Perhaps status text collapsing should be changed to match this.

Fixes: AkkomaGang/akkoma-fe#495
2026-05-31 00:00:00 +00:00
Oneric
167c65d796 poll: allow viewing results without voting
Instead of needing to view post in a separte context without login
or going to the source instance for remote posts. There’s in theory
and argument to be made about knowing the current results influencing
one’s own vote, but (a) it’s already possible to view results anyway,
just mroe convoluted and (b) sometimes one has no opinion or does not
fit the criteria for desired voters and thus won't/shouldn't vote but
is still interested in seeing what eligible voters thought.

Disabling the show result button if in the process of voting
is mostly just meant to prevent misclicks when trying to submit
ones vote. Since both end up showing the results the mistake might
go easily unnoticed.
2026-05-31 00:00:00 +00:00
Oneric
f646beb1bf changelog: add missing mfm entry
Implemented in 69d9b1c1e6
(and related backend changes)
2026-05-31 00:00:00 +00:00
Oneric
5f8d0d1d8f search_form: add search syntax hint
With the user-oriented FE docs being down,
it’s otherwise quite hard to discover the syntax.

Ideally the backend would provide the localised syntax description
for just the actually active provider. Then there’d be no need for
regular users to care about the configured backend provider.
If the (english version of the) description is located in the
same module as the provider logic, the chances of it staying
in sync with logic tweaks and other updates is much higher too.
Furthermore, this would allow alternative backends like Iceshrimp.NET
to provide an appropriate syntax hint without special client effort.

However, this requires much more back- and frontend plumbing.
Future contributions to improve it in this regard are warmly welcome,
but until someone is willing to work through all the necessary bits,
an exhaustive, purely client-side hint is at least better nothing at all
2026-05-30 00:00:00 +00:00
Oneric
a69b8fd3ff api/search: determine if more results without refetch
To ensure this works with different or future revisions of backends,
this explicitly sets the default limit for a reliable comparison target.
2026-05-29 00:00:00 +00:00
Oneric
708940634a search: support pagination
For performance reasons, limit it only to single-type queries though.
This avoids requerying already finished or irrelevant types
over and over again. In the future once the backend supports it
we might even be able to utilise id-based key pagination instead of
the more costly offset pagination.

Matching the default backend config,
unauthenticated users are not allowed to paginate.

Due to using offset pagination and the API result contianing several
independent lists, we cannot use the existing loadMore infrastructure
instead requiring bespoke pagination logic and UI.
2026-05-29 00:00:00 +00:00
Oneric
a5e17852fd search_form_modal: place closer to the top
Since the button to open it is at the very top
this makes it more convenient to use
2026-05-28 00:00:00 +00:00
Oneric
be8421064f mfm: fix display of bg directive
Without it the background colour doesn’t actually
cover the whole text’s background.

Fixes e.g. https://sk.nomaakip.xyz/notes/amlcivx7a7bf009j
2026-05-28 00:00:00 +00:00
Oneric
0cbbcd4e38 Improve search UI
We no longer force searching all types at once
and finally expose some control knobs the backend
supported for ages already.
This can make search faster (no need to search categories
you don’t care about) and more precise.

Furthermore, logged-out users will no longer be able to request
fetching _new_ remote content matching the default restrictions
of the backend.
Due to inflated processing cost, logged-out users also won't
be able to search all categories at once. Though, this is not
hard enforced and can be circumvented via query URL parameters.
2026-05-24 00:00:00 +00:00
Oneric
dccfece32e checkbox: change cursor when disabled
Depending on theme the colour difference
for disabled custom checkboxes might be
onl minor and easy to miss
2026-05-23 00:00:00 +00:00
Oneric
69d9b1c1e6 mfm: extend MFM support
This should now cover everything Iceshrimp.JS supports
with the exception of sparkle and followmouse if encoded properly.
(Or at least, everything advertised in it’s "mfm-cheat-sheet" page.)
The display for unixtime differs a bit, but matching it exactly should
not be critical as it seems not too useful for complex MFM animations.

The followmouse directive requires client-side scripts to be permanently active
and to make sense components must be allowed to leave their status. Both are
not desireable; no support for it is planned.

As implemented by Misskey, sparkle spawns and removes new animated SVG elements
at seemingly random positions around the source. We do not want permanently active
client scripts, so this cannot be copied.
It might be possible to replace this with a static animation yielding a
suffiently similar appearance. However, we’ll still need to add
additional elements during load for this which will cause issues if MFM
support is disabled or changes enabled state at runtime.
Alternatively limit ourselves to at most two simultaneous sparkles
(per element)  relying on animated ::before and ::after pseudo elements.
2026-05-22 00:00:00 +00:00
Oneric
f581364a71 status_content/mfm: sort CSS rules mostly alphabetically
This makes it easier to determine what is already supported.
Also fix precedence on conflicting parameters.
2026-05-22 00:00:00 +00:00
Oneric
a9d33a4904 user_card: improve banner styling
No longer will long bios end up zooming in
on just a tiny fraction of the banner.
Instead always fit it to the width and let the height adapt as needed.

This however means we can no longer rely on the image filling the
entire height, therefore gradient and masking logic for smooth
transitions had to be reworked as well.
2026-05-15 00:00:00 +00:00
2fb78f8727 Merge pull request 'rich_content: add padding on codeblock' (#515) from Yonle/akkoma-fe:codeblockimprv-1 into develop
Reviewed-on: AkkomaGang/akkoma-fe#515
2026-05-06 15:08:13 +00:00
07e72ff915 rich_content: add padding to codeblocks
Else code text is adjoined to the vertical codebock border.

Also use explcitit "scroll" for odd browsers completely hiding the
scrollbar otherwise. One could argue "auto" still being more correct
since this matches what is evidently the desired and preferd styling
by those browser vendors. But on the other hand user confusion from
this questionable styling decision seems more problematic and already
occurred.
2026-05-06 00:00:00 +00:00
4a33df4874 update changelog 2026-05-04 20:09:40 +01:00
Oneric
3474a4e2b9 status: fix line wrapping for MFM
Commit 65201e3000 added the mfm class
(back) to MFM posts to adjust emoji sizing and commit
ebe4158885 changed codeblocks to respect
the original line breaks allowing to scroll the code container.

However, we still carry CSS to display legacy MFM representations
and one of the legacy rules matched on just the mfm class changing
display to inline-block.
This caused code blocks to not become scrollable, but simply overflow
while at the same time pushing the logical line length for all other
text in the statuses so it may overflow too. This overflow then got
truncated making the post unreadable.

Fix this by renaming the MFM post indicator class
to avoid any overlap. This may potentially leave legacy MFM broken,
but further research needed to figure out why it chooses inline-block
display in the first place.

Mostly-resolves: AkkomaGang/akkoma-fe#511
2026-05-04 00:00:00 +00:00
Oneric
f85d394fd9 Fix MathML annotation rendering
By default DOMPurify removes enclosing semantics and annotation
elements but retains their children by reparenting them.
This led to supposed to normally invisible source annotations
being rendered next to the proper math expression.

Fixes oversight in: d0135f4a71
Fixes: AkkomaGang/akkoma-fe#509
2026-04-30 00:00:00 +00:00
89130d2dc7 Merge pull request 'User suggest (mention autocomplete) fix' (#507) from Oneric/akkoma-fe:fix-user-suggest into develop
Reviewed-on: AkkomaGang/akkoma-fe#507
2026-04-26 15:36:19 +00:00
Oneric
86ac627422 emoji_input/suggestor: don't trim leading @ from account search query
There is no issue with passing it. On the contrary, it lets the
backend know we are looking for matching nicknames specifically.
2026-04-24 00:00:00 +00:00
Oneric
556caba534 emoji_input/suggestor: don’t skip searching for remote accounts
There is no issue on current backends and
this simply doesn’t make any sense.
It lead to sometimes remote accounts _never_ showing up in the suggestor
if they were not in the first page of results for just the local
username part of their handle unless the account was already loaded
into cache by some other means beforehand.
2026-04-24 00:00:00 +00:00
Oneric
a2155a3c10 emoji_input/suggestor: only suggest already known users
We don’t want to kick of a barrage of remote request on the backend
attempting to lookup half incomplete nicks. Nor do we, in the client,
want the additional latency of waiting for the backend to finish those
futile network requests.

Eventhough the suggestor used to call the API with an explicit
resolve=true, no actual WebFinger lookups should’ve ever been
made due to the suggestor (otherwise) nonsensically stopping
backend queries if there was a remote domain fragment.

(Yes, "emoji_input" also handles user suggestions)
2026-04-24 00:00:00 +00:00
Oneric
6bff83f112 settings/profile_tab: fix avatar upload
Fixes oversight in 4d578720e8.

Avatar upload logic is wrapped in an explicit Promise object and
thus cannot access the tab’s this object. Instead it uses an
indirection via a captured local variable "that" for all existing uses,
but this was overlooked for the added bits.

All other uploads do not use a cropper and are not affected

Fixes: AkkomaGang/akkoma-fe#505
2026-04-18 00:00:00 +00:00
1edd7cb379 Merge pull request 'Small visual tweaks' (#504) from Oneric/akkoma-fe:visualtweaks into develop
Reviewed-on: AkkomaGang/akkoma-fe#504
2026-04-14 21:40:18 +00:00
5ba3be783b Merge pull request 'Improve style for quoted text in RichContent' (#412) from ilja/akkoma-fe:improve_visual_style_for_quoted_text into develop
Reviewed-on: AkkomaGang/akkoma-fe#412
Reviewed-by: Oneric <oneric@noreply.akkoma>
2026-04-14 21:05:22 +00:00
15b52da145 Merge pull request 'fix spacing after mentions' (#503) from Oneric/akkoma-fe:richparser_mentionpadding into develop
Reviewed-on: AkkomaGang/akkoma-fe#503
2026-04-14 21:04:58 +00:00
2c252bb6bf Merge pull request 'Replace awful and buggy in-house HTML parser' (#501) from Oneric/akkoma-fe:htmlparse into develop
Reviewed-on: AkkomaGang/akkoma-fe#501
2026-04-14 20:02:30 +00:00
Oneric
ebe4158885 rich_content: improve codeblock styling
Using `--input` for codeblock backgrounds is not ideal, but the best we
can do with currently existing variables. Using colours not nominally
intended for text background risks borking on custom themes.
Among actual background colors we have
 - the regular post background `--bg` which never provides distinction
 - the background of _selected_ posts `--selectedPost` which
   only offers a visual distinction for not selected posts
 - Background of profile `--profileBg` which is only used in absence of
   a profiel banner image — but Mastodon API requires us to _always_
   have a banner image (so this color is not actually used and thus has
   a higher chance of breakage)
 - the background of input fields `--input`

The latter is well tested and almost certainly well legible
with a decent chance of creating a visual distinction.

Future patches to create a distinct new colour variable for codeblocks
and the plumbing in the theme editor welcome.
2026-04-14 00:00:00 +00:00
Oneric
c7d7fd5fdd rich_content: overhaul spacing around mentions
The old code was inconsistent about when the mention line state and
remembered spacing were reset as well as failing to add necessary
whitespace if a mention line was immediately succeeded by a plain text node.
The latter was kludgily fixd for a particular special case with the
last-child exception to whitespace trimming. (However this could en up
to retaining superfluous undesireable space in other cases)

This led to sometimes space being added "back" several times
notably leading to one extra, empty line in blockquote elements
succeeding mentions with whitespace. This stalled the otherwise
unrelated AkkomaGang/akkoma-fe#412

Now we always reset the mention chain and remembered spacing together
and also add back spacing in front of plain text nodes if appropriate
obsoleting the last-child exception.
2026-04-14 00:00:00 +00:00
Oneric
65201e3000 status: always match key’s emoji size for MFM
Previously it was only adjusted for effects involving scaling,
creating odd inconsistencies betwen e.g. an unstyled emoji and
a slow "tada" animation as well as reduced compatibility.

To ensure scaling still works for posts with FEP-c16b-like HTML
without the source type being actually indiated as (vanilla) MFM,
the 2em rule specific to scaling tags is retained too.
2026-04-14 00:00:00 +00:00
Oneric
16de7133da user_profile/fields: allow linebreaks at whitespace
Long'ish values and keys are not too uncommon and this lead
to them becoming unreadable, the full text only accessible as a tooltip.
Overly long single words are still ellipsised to prevent overflow.
2026-04-14 00:00:00 +00:00
Oneric
aa567c446b config: default conversationDisplay to tree mode 2026-04-14 00:00:00 +00:00
Oneric
f3907fe0be unittest/rich_content: fix MFM injection tests
Just like the old NIH HTML parser this test wrongfully assumed
">" couldn't appear unescaped in tag attributes and thus was
broken for many months now after a browser upgrade in our CI runners
caused this to be quoted automatically anyway.

Instead test against actual injection attempts
of additional styling rules and new attributes.
2026-04-13 00:00:00 +00:00
Oneric
d0135f4a71 Sanitize HTML again client side
The backend is already supposed to sanitize everything for us
and it is rather restrictive even. But it’s simple enough,
so lets make sure of it and lessen the real-world impact
should there ever be backend sanitizer bugs (if at all,
then most likely in fast_html or its HTML parser dependency)

DOMPurify can only return a body element or a DOcumentFragment
not a full document as DOMParser does. However we only ever
grabbed the body object anyway so let’s just return that directly.
2026-04-13 00:00:00 +00:00
Oneric
87ef8eeaf1 Replace awful and buggy inhouse HTML parser
The inhouse parser wrongly assumed tag attributes could not contain
literal '>' characters, has issues like the recently fixed
AkkomaGang/akkoma-fe#480 and probably many
more problems. In any event the regex and characterwise parser mess was
hard to read and reason about.

Browsers already have optimised HTML parsers built in so use it.
Also rework the nested function into a parser class for even better
readability.
Other than using a proper HTML AST and rearranging everything into
a class this for the most part closely follows the previous logic.
More substantial logic changes were made to emoji processing and
green/cyantext styling.
The former now processes text in chunks to the next colon rather
than character by character. The latter is possibly a bit more
leient in what it styles now and no longer will break styling
tags enclosing a <br />.
2026-04-13 00:00:00 +00:00
Oneric
b193fd5adf rich_content: drop unused writtenTags
Just like lastTags, it was added in aec867b300
but never actually used anywhere
2026-04-10 00:00:00 +00:00
Oneric
1a124db024 rich_contnet: drop second, reverse direction parser pass
The reverse pass basically only dealt with hastags and existed
to make detection of the "last" set of consecutive hashtags easier.
But since lastTags was never used and thus dropped in the preceding
commit, the whole pass is obsolete too.
We can simply parse hastags during the forward pass.
2026-04-10 00:00:00 +00:00
Oneric
0b2ff901ca rich_content: remove unused lastTags
The porperty was added in aec867b300
and accomodated for in f883d2f75cd3c404115bd2c98b6d3c8d7ff10ef6’s
refactor, but was actually never used anywhere.
2026-04-10 00:00:00 +00:00
Oneric
65a51a2596 Replace bugged custom HTML stripper
HTML tags can in fact contain '>' characters in quoted attribute values.
Besides that the regex replace failed to normalise whitespace
or to strip invisible elements. And lodash’s 'unescape' function
only replaces a limited set of quoted HTML entities, not all.
2026-04-10 00:00:00 +00:00
Oneric
ddcbaa7256 status_content: drop obsolete condition
The computed localCollapseSubjectDefault was removed from status_content
about five years ago in 50aa379038. Ever
since this clause was effectively dead code but spamming Vue warnings.

It used to automatically reveal NSFW attachments already hidden behind
a collapsed content warning. However, this means it was impossible to
reveal just the text of a content warned post and we already have a
separate settings for always revealing NSFW media if desired, although
this admittedly will also reveal cw-less media directly.

Given there seemingly were zero complaints about the effective new
behaviour, let’s just drop the dead code conditional.
2026-04-10 00:00:00 +00:00
Oneric
c57e59f8e8 poll: show vote anonymity hint if known
Ref: AkkomaGang/akkoma#1104
2026-04-04 00:00:00 +00:00
061a9ad325 Merge pull request 'Profile media alt texts' (#499) from Oneric/akkoma-fe:profile-media-alt-text into develop
Reviewed-on: AkkomaGang/akkoma-fe#499
2026-04-04 12:15:05 +00:00
Oneric
4d578720e8 settings/profile: allow setting media alt texts
Ideally alt text and image would be handled together, but this is not
straightforward to do with the current profile tab and API interaction
setup.

To somewhat alleviate this, always submit the current matching alt text
too when uploading a new image. This allows an alt text edit before
confirming the upload to immediately take effect without the user
needing to remember to go back to the upper section to hit "save" there.
2026-04-04 00:00:00 +00:00
Oneric
c98962f4b3 user*: expose profile media alt texts
Only avatar alt text is integrated into the UI in an assistive way.
Header and backgrounds are set as CSS backgrounds and I don’t know
of a good way to add alt or aria-label attributes to that. Nor whether
it even makes sense to bake this into the default view since their just
decorative background elements.
The full al text can still be accessed through the new profile media gallery.

Despite many places setting distinct :title and :alt atttributes
for StillImage, it actually only had a :alt attribute used for both.
This is however not what we want here,
so add (back?) :title as a distinct property.

Related backend change: AkkomaGang/akkoma#1034
2026-04-04 00:00:00 +00:00
Oneric
2292381b0a ci: move to ARM runner
Our ARM runner is both faster and less used than the x86 runner.
Nothing here is specific to x86 though, so let’s make use of the ARM one
2026-03-28 00:00:00 +00:00
Oneric
0f695386fe list: expose exclusive parameter
Allows excluding list members from home timeline.
Matches Mastodon and implemented in Akkoma via
AkkomaGang/akkoma#1062
2026-03-28 00:00:00 +00:00
Oneric
7fb67ee723 service/file_type: fallback to generic Masto type
E.g. bridgy doesn’t federate the full MIME type and
it’s attachment URLs also have no extension. Thus
the full MIME type is always just a generic binary,
but since it still federates a more specific AP type
the generic Mastodon type still contains some information
we can use here to display it properly.

While at it, drop unused fileMatchesSomeType function.
Its last users disappeared in e654fead23.

Ref.: https://github.com/snarfed/bridgy-fed/issues/2198

Co-authored-by: Yonle <yonle@proton.me>
2026-03-27 00:00:00 +00:00
Oneric
e80ebc3fac changelog: add missing entries 2026-03-21 00:00:00 +00:00
e6c0d35d29 Merge pull request 'notification: fix code usage on mobile' (#492) from Yonle/akkoma-fe:mobilenotif-fix1 into develop
Reviewed-on: AkkomaGang/akkoma-fe#492
2026-03-16 13:41:01 +00:00
8f5cf700f8
module(users): remove unnecessary check on getNotificationPermission 2026-03-16 15:11:46 +07:00
Oneric
efe15c98c6 lists: ensure all properties exist after creation
This used to cause null errors e.g. when initialising the accounts for a
newly created list, which also prevented a post-creation redirect to the
new list’s page from occuring.

Co-authored-by: Yonle <yonle@proton.me>

Fixes: AkkomaGang/akkoma-fe#367
Fixes: AkkomaGang/akkoma-fe#368
2026-03-15 00:00:00 +00:00
Oneric
a734eda0d9 Bump version for release 2026-03-14 00:00:00 +00:00
51caf0430f
notification: fix code usage on mobile
on mobile (especially PWA), window.Notification is illegal to use. so if possible, consider using serviceWorker instead.
2026-03-10 12:47:18 +07:00
48905a4431 Merge pull request 'a fix for nsfw warnings display on webkit' (#488) from mkljczk/akkoma-fe:webkit-fix into develop
Reviewed-on: AkkomaGang/akkoma-fe#488
2026-03-06 16:30:12 +00:00
c465cb0a35 components/attachment: fix display of nsfw overlays on webkit 2026-03-06 00:00:00 +00:00
Oneric
affbc240d1 changelog: add everything since 3.17 (2025.12) 2026-03-02 00:00:00 +00:00
a123b41a2f Merge pull request 'Fix HTML attribute parsing for escaped quotes' (#480) from mkljczk/akkoma-fe:get-attrs-fix into develop
Reviewed-on: AkkomaGang/akkoma-fe#480
Reviewed-by: Oneric <oneric@noreply.akkoma>
2026-02-19 12:31:59 +00:00
4ab3424508 Fix HTML attribute parsing for escaped quotes
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-16 13:36:15 +01:00
b04e4810f8 Fix HTML attribute parsing, discard attributes not strating with a letter
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-16 13:30:52 +01:00
fc8debd2c4 Merge pull request 'components/quote_button: show for local and own private posts' (#478) from Oneric/akkoma-fe:more-quoting into develop
Reviewed-on: AkkomaGang/akkoma-fe#478
2026-02-07 22:40:22 +00:00
Oneric
8227c84aa2 components/quote_button: show for local and own private posts
Aligning to AkkomaGang/akkoma#1059
2026-02-07 00:00:00 +00:00
Oneric
42595fcb2c cosmetic: fix linter complaints
Mostly just reordering, whitespace changes
and removing superfluous "this".

eslint really wants us to add :key to the UserAvatar list in DM
conversation cards. With :key Vue will reorder elements instead
of patching their contents on list changes, allowing input state
of elements to be preserved. This doesn’t really seem relevant
here since USerAvatars do not have a state, but also not harmful.

One lint complaint about using double quotes at the outer level
was purposefully ignored as it results in needing to quote
double quotes within the string making it rather unreadable.
2026-01-26 00:00:00 +00:00
Oneric
e3a72827ef side_drawer: add entry for bookmarks
It was not easily available in the narrow "mobile" interface
until now since both the desktop_nav and top nav panel are hidden.
Placing bookmarks after lists is consistent with the top nav panel
(though the top nav panel also puts interactions before both).

The recently removed "direct" timeline was similarly unavailable,
but its replacement, dm conversations, was already added to the
side drawer upon its introduction.

Fixes: AkkomaGang/akkoma-fe#474
2026-01-25 00:00:00 +00:00
34e4928754 Merge pull request 'polls: don't continuously refresh closed polls and refresh less frequently' (#472) from Oneric/akkoma-fe:poll-upd-frequency-reduction into develop
Reviewed-on: AkkomaGang/akkoma-fe#472
2026-01-24 18:29:59 +00:00
Oneric
9bfd3936d6 polls: do not fetch updates for closed polls 2026-01-14 00:00:00 +00:00
Oneric
8d8e6d979a polls: reduce frequency of update fetches
Thirty seconds is much quicker than any other auto-refreshes in the
interface. Emitting a request for every users and tab with the poll
loaded this frequently can add up to a noteworthy total on the backend.

This is significantly worsened by our backend currently synchronously
fetching and updating the status of remote polls when queried about
while initiating a remote fetch for every incoming request inbetween
the last refetch passing the  age threshold and the first current fetch
suceeding and completing its db transaction.
2026-01-14 00:00:00 +00:00
e52157042d Merge pull request 'migrate to conversation API (replaces "direct" timeline)' (#470) from Oneric/akkoma-fe:conversations-api into develop
Reviewed-on: AkkomaGang/akkoma-fe#470
2026-01-11 15:55:01 +00:00
Oneric
9b45a382b0 Show total count of unread DM conversations in sidebar
In preparation to an eventual switch to native Masto API format,
as well as flexibility to backends without the extension,
the entity normaliser just copies the count paramter to the same path
2025-12-30 00:00:00 +00:00
Oneric
d73d7a2a0d Automatically update follow request count shown in sidebar
The current user info fetcher will also be used for
total unread DM counts in the future.

To avoid any future mishaps with improperly stopped fetchers etc,
this  does not use the existing 'setCurrentUser' method but a new
update method with a safeguard against accidentally changing the
identity.
2025-12-30 00:00:00 +00:00
Oneric
252f8c5e2d components/side_drawer: drop call to non-existent action
This is the only place such an action is referenced
2025-12-30 00:00:00 +00:00
Oneric
8d24b877e0 Allow editing DM conversation recipients
Unfortunately the backend currently only accepts query parameters here
instead of JSON bodies as preferred by almost all oter endpoints.
2025-12-30 00:00:00 +00:00
Oneric
b3b998fd1f components/dm_conv_timeline: show conversation details in header
This also allows marking the conversation
as read etc from its timeline view.
2025-12-30 00:00:00 +00:00
Oneric
2e53ee6536 components/dm_conv_card: make compactness and status preview configurable 2025-12-30 00:00:00 +00:00
Oneric
9fdf2d22a7 By default require confirmation to delete a dm conversation 2025-12-30 00:00:00 +00:00
Oneric
df9bb44e14 Allow deleting conversations from overview 2025-12-30 00:00:00 +00:00
Oneric
3d4c79b344 Allow marking conversations as read 2025-12-30 00:00:00 +00:00
Oneric
0da1a32767 Add basic UI for conversation API
This replaces the removed "direct" timeline.
Curtnetly this is a read-only interface missing ways to
mark conversations as read, dismiss/delete conversations
or modify the core members of a conversation.

Future work may also add QoL stuff like automatic implicit addressing of
core members or (provided another backend extension) add messages to a
conversation without replying to something particular.
2025-12-30 00:00:00 +00:00
3a20ec5162 Merge pull request 'show more reblog info' (#468) from Oneric/akkoma-fe:reblog-info into develop
Reviewed-on: AkkomaGang/akkoma-fe#468
2025-12-22 16:48:21 +00:00
Oneric
28d0a30888 Remove DM timeline
It has been long deprecated and even already removed from Mastodon
and our existing implementation suffers from bugs (at least on
large/some instances), see:
   AkkomaGang/akkoma#798

Except for pleroma-derived web frontends, other clients generally don't
seem to make use of this timeline either, often also omitting an
interface for the conversations API.

Even if it worked properly, this isn’t the best way to present
DMs as all threads with different participants or topics are mixed
together in one linear timeline. The conversations API which suceeded
it in Mastodon and our backend already supports offers a much better
interface.
2025-12-22 00:00:00 +00:00
Oneric
2fb38a597c service/api: fix encoding of arrays in query parameters 2025-12-22 00:00:00 +00:00
2760495b54 Update README.md 2025-12-21 03:57:46 +00:00
2ef333dafc Merge pull request 'Fix setting persistence to local browser storage' (#469) from Oneric/akkoma-fe:fix-settings-local-persistence into develop
Reviewed-on: AkkomaGang/akkoma-fe#469
2025-12-19 19:20:14 +00:00
6d260c08c0 Merge pull request 'Improve still image detction without media proxy' (#466) from Oneric/akkoma-fe:still-image-accuracy into develop
Reviewed-on: AkkomaGang/akkoma-fe#466
2025-12-19 17:39:25 +00:00
Oneric
7456b8b02f components/status: show when a reblog happened 2025-12-19 00:00:00 +00:00
Oneric
d83fd8b1cd Fix setting persistence to local browser storage
Fixes regression in f2c55423fd.
Ideally, this would only set state for what was actually changed
rather than rewriting the entire storage each time, which would also
have avoided this issue.

The fix is somehwat hacky not working with an empty path list or
parsed/internal fields not at the top-level of a path, but in practice
this seems to be always called with well bheaving paths. Should this ever
become an issue, this should migrating to the overall saner updating
approach described in the preceding paragraph.

Note: the cloneDeep call was originally added in
a97c07bfdf as part of
https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1385
Though it is not clear why setState would mangle its data argument
as it supposedly does to necessitate this copying.
2025-12-19 00:00:00 +00:00
Oneric
8f948d52f5 still-image: remove excessive pixelArt detection log 2025-12-19 00:00:00 +00:00
tea
9a671325ad components/status: display repeat visibility
Cherry-picked-from: ac9c512c48
2025-12-19 00:00:00 +00:00
Oneric
26a4188620 still-image: always fetch local content for animaton detection
Fixes: AkkomaGang/akkoma-fe#381
2025-12-19 00:00:00 +00:00
Oneric
b839a25060 still-image: detect animated APNG by extension
If they use the canonical apng extension specified by IANA.
2025-11-26 00:00:00 +00:00
09fba6bada Merge pull request 'readme: Update corepack install command' (#463) from norm/akkoma-fe:readme-corepack into develop
Reviewed-on: AkkomaGang/akkoma-fe#463
Reviewed-by: Oneric <oneric@noreply.akkoma>
2025-11-23 11:50:03 +00:00
4d85f0a074 readme: Update corepack install command
As of Node 25, corepack will no longer be included by default in
the distribution:
https://github.com/nodejs/TSC/pull/1697

A separate corepack package is already available in npm, which
should work the same as the corepack that was included in Node 24
and prior.

The rest of the build instructions should remain the same.
2025-11-17 11:38:01 -05:00
a1e83062b4 Merge pull request 'Allow using regex filters for mutes' (#461) from Oneric/akkoma-fe:regex-mute-filter into develop
Reviewed-on: AkkomaGang/akkoma-fe#461
2025-11-09 13:16:04 +00:00
Oneric
4315788019 Add support for regex mute filters
This makes any filter that starts and ends in forward slashes act as a
regex filter instead of a simple substring filter.
In case any existing plain-text rule already matches this
it will need to be updated adding an additional layer of slashes
and escapes as needed. However, this is thought to be sufficiently uncommon.

Instead of using trailing flags as modifier complicating parsing further,
any modifications to regex matching must be done via local modifiers. See
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Modifier
2025-10-26 00:00:00 +00:00
Oneric
f2c55423fd config: add infrastructure to cache parsed config values
Using muteWords as an example.
Currently this doesn’t help much
but the subsequent commit will extend muteWord capabilities
making parsing more costly
2025-10-26 00:00:00 +00:00
9bbc68536c Merge pull request 'Fix internal boost visibility scope' (#455) from NixLynx/akkoma-fe:boost-fix into develop
Reviewed-on: AkkomaGang/akkoma-fe#455
Reviewed-by: Oneric <oneric@noreply.akkoma>
2025-10-25 18:17:30 +00:00
Oneric
c5f068d6fa scope_utils: replace compare with isSubScope
Visibility scopes have no total order, but the function signature
of a compare function implies/requires such.

This led to the scope selector showing "local" as a possible option
on some original post scopes it was not actually compatible with.
The negotiated initially selected value was already correct though.
2025-10-25 00:00:00 +00:00
Nix Lynx
98826e8462 Fix internal boost visibility scope
Fixes d617a9596a
2025-10-25 00:00:00 +00:00
b13ecbcf6f Merge pull request 'Various small fixes' (#459) from Oneric/akkoma-fe:varfixes-20251018 into develop
Reviewed-on: AkkomaGang/akkoma-fe#459
2025-10-24 18:56:42 +00:00
66b026561f Ensure attachment descriptions fill full width
Was noticeable by e.g. the scrollbar being shown somewhere inside the
container and not being able to scroll near the edges if each line of
text was sufficiently short.

Directly implements the suggesstion from
AkkomaGang/akkoma-fe#456 (comment)
2025-10-24 00:00:00 +00:00
Oneric
c925f7f91b Fix failed media uploads bricking the frontend
The upload call hid errors and the entity normaliser lets them pass
through as well (assuming it must be an already "correctly" formatted
legacy qvitter response), which led to errors being added to the the
draft as if it was a valid attachment object.
On later use of this error exceptions occur breaking the frontend and
due to being saved as part of a draft this could only be recovered by
clearing local client data.

Fixes: AkkomaGang/akkoma-fe#339
2025-10-24 00:00:00 +00:00
Oneric
c3673eb53a Always use AP ID when copying post link
This is the canonical reference to a post and works best for lookups on
other servers. Previously this canonical ID was not accessible from the
frontend at all.

The "source" button continues to redirect to the preferred display URL
though (if set), since this is as the name suggests the preferred URL
for viewing the post in a browser (it might however not work when the
source instance restricts unauthenticated access even to local content).

This lifts the redundancy between the "source" and "copy link" buttons.

Since the legac qvitter API is still accounted for in the entity
normaliser, we just assume its external_url serves as both the canoncial
location and preferred display URL. It is dubious however, if this
codepath can even still be triggered at all and it likely makes more
sense to purge all remnants of the legacy API from the codebase.

Resolves: AkkomaGang/akkoma-fe#166
2025-10-18 00:00:00 +00:00
Oneric
f0c149950c Fix image attachments overflowing their container
This reverts commit e1b4d8f59a
and obsoletes commit c2db0e66ef
which already unset min-height in notifications where this
caused images to become effectively invisible.

Image previews are currently designed to always show the full image
scaled down as needed. With min-height though they were allowed to
take the full width even if it caused overflows in vertical direction.
This happened to look kind of fine with only easy-to-miss overflows
in the main post view if each preview row had the same amount of
columns, but creates jarring overlaps otherwise.

Fixes: AkkomaGang/akkoma-fe#456
2025-10-18 00:00:00 +00:00
3d54c8274f Merge pull request 'media_modal: fix image load handler wiring' (#454) from novenary/akkoma-fe:media_modal/load_handler into develop
Reviewed-on: AkkomaGang/akkoma-fe#454
2025-10-14 12:40:54 +00:00
novenary
f6bf484d4b media_modal: fix image load handler wiring
The media modal was changed to use still_image, but the attribute
pointing to the image load event handler wasn't updated.
This causes the modal to remain in the loading state forever.

Fixes: f48138c979
2025-10-14 15:36:22 +03:00
57a809946c Merge pull request 'Don’t litter tokens and Iceshrimp.NET support' (#452) from Oneric/akkoma-fe:frugal-tokens-and-iceshrimp.net into develop
Reviewed-on: AkkomaGang/akkoma-fe#452
2025-10-13 12:22:33 +00:00
2f64931d5b Merge pull request 'Show inline link for unresolvble posts and quoted quotes' (#453) from Oneric/akkoma-fe:quote-doesnt-exist-and-cant-hurt-you into develop
Reviewed-on: AkkomaGang/akkoma-fe#453
2025-10-13 12:17:48 +00:00
Hannah Ward
c2db0e66ef unset min-height for attachments in notifications 2025-10-13 13:07:57 +01:00
762676e105 and again 2025-10-13 10:49:35 +01:00
1fa242232e bump version 2025-10-13 10:48:46 +01:00
Oneric
e71da57845 Show inline link for unresolvble posts and quoted quotes
Previously those two cases just weren’t recognisable as quotes at all.

Fixes: AkkomaGang/akkoma-fe#310
2025-10-13 00:00:00 +00:00
Oneric
877dde80c9 user_card: don't set replied-to-status id to a boolean
Instead, just also prefill the text wuth the user handle
for pure mentions.
This lead to us sending a boolean status id to the backend
which presumably only didn't cause problems in *oma backends
because API spec validation dropped the value with a mismatched type.
On an Iceshrimp.NET backend this caused errors to pop up.

There are more conditions gated by replyTo in the post form but
no longer triggering them doesn't seem to have any noticeable effect.
The one noticeable change being mentions now share a draft save slot
with regular message composing (before all mention messages shared the
same reply:true slot), but this seems sensible enough.
2025-10-12 00:00:00 +00:00
Oneric
f885728ccd Lock package manager version
At least modern versions of corepack'ed yarn
(as build instructions tell use to use)
reaaally want to add this to the lockfile,
so let it do so to not need to constantly
be wary of uncommited changes here.
2025-10-12 00:00:00 +00:00
Oneric
4cb74a3fbe Clean up app tokens on logout
We don’t need them after initial account registration
and they just clutter the database otherwise

Together with the preceding commit this should get the
flood of garbage tokens into our database under control.
(We still want to fix the backends cleanup of old,
 already existing tokens though)

Fixes: AkkomaGang/akkoma-fe#429
2025-10-12 00:00:00 +00:00
Kopper
e79916e78e Do not create OAuth app until login
Original commit amended with a fix for registrations;
they need to (now) create an app and fetch an app-only token
before doing anything else, as this endpoint requires such a token.

Co-authored-by: Oneric <oneric@onierc.stub>

Cherry-picked-from: 0e25b94186
2025-10-12 00:00:00 +00:00
Kopper
3881f87c79 Properly pass credentials for follow requests and followed hashtags
This only worked in *oma due to legacy session-cookie auth kicking in
(which we should really get around to fully purge).

Cherry-picked-from: e8896fad15
2025-10-11 00:00:00 +00:00
Kopper
82647e8e98 Accept full URLs for /api/v1/pleroma/emoji
The frontend assumes these URLs will be relative to the target instance,
which may not be the case for non-*oma backends like Iceshrimp.NET
due to the backend storing emoji in an external object storage depending
on configuration.

Cherry-picked-from: c147c2aeb3
2025-10-11 00:00:00 +00:00
Weblate
539977de9d Merge branch 'origin/develop' into Weblate. 2025-10-04 22:05:59 +00:00
d2995ada16 Merge pull request 'Misc fixes' (#416) from novenary/akkoma-fe:misc-fixes/2024-09-17 into develop
Reviewed-on: AkkomaGang/akkoma-fe#416
Reviewed-by: Oneric <oneric@noreply.akkoma>
2025-10-04 22:05:56 +00:00
Weblate
bcd15ef858 Merge branch 'origin/develop' into Weblate. 2025-10-04 22:04:27 +00:00
900ac68ca6 Merge pull request 'Use pixelated (up)scaling for custom emoji' (#448) from Riedler/akkoma-fe:pixemoji into develop
Reviewed-on: AkkomaGang/akkoma-fe#448
Reviewed-by: Oneric <oneric@noreply.akkoma>
2025-10-04 22:04:24 +00:00
Weblate
34bbcef83e Merge branch 'origin/develop' into Weblate. 2025-10-04 22:04:15 +00:00
37ce8352a9 Merge pull request 'fix multiline alt texts for generic attachments (e.g. zip files)' (#446) from Riedler/akkoma-fe:fix-attachalt into develop
Reviewed-on: AkkomaGang/akkoma-fe#446
Reviewed-by: Oneric <oneric@noreply.akkoma>
2025-10-04 22:04:11 +00:00
f48138c979 oops! added it to media_modal as well 2025-09-24 18:04:43 +02:00
5baa2ce40f apply pixel art detection to more places 2025-09-24 17:28:29 +02:00
novenary
fef531b8a0 conversation: scrollIntoView when collapsed
This helps find the original context when collapsing a thread on the
timeline.
2025-09-24 15:37:06 +03:00
bf0c137057 conditionally render small emojis as pixelated 2025-09-24 13:49:16 +02:00
5a50ceb3aa Use pixelated (up)scaling for custom emoji 2025-09-24 13:13:30 +02:00
f08a961199 fix: some days I hate CSS 2025-09-22 17:56:16 +02:00
d252e10543 fix: scrollable gallery rows for if contents are too long
like my peanits
2025-09-22 16:23:33 +02:00
novenary
7c84854b10 post_status_form: inherit language from parent
If I'm replying to a post in Klingon, chances are I'm going to write in
Klingon. This reduces friction for properly marking post language in a
conversation.
2025-09-20 11:10:01 +03:00
novenary
ab606c6160 post_status_form: reset all to defaults on clear 2025-09-20 11:10:01 +03:00
novenary
38d8a9751a emoji_picker: select recents tab by default
This saves a click to get at your most commonly used emoji.
2025-09-20 11:09:59 +03:00
novenary
2c92467dcd post_status_form: fix enter key in subject field
This fixes random actions being triggered by the enter key while the
subject field is focused.

When pressing enter, the browser simulates a click on the first "submit"
button it finds in the form.
A submit button is a button without `type="button"` set.
Remediate this by setting the type attribute on all but the "Post"
button.

Additionally, inhibit the enter key in the subject field (ctrl+enter
still works).
2025-09-17 23:43:55 +03:00
bb71635d12 fix: no multiline alt text in post popovers 2025-09-10 03:13:13 +02:00
e1b4d8f59a fix: minor overflow issue in draft attachment alt text 2025-09-10 02:35:14 +02:00
2455bb70f3 feat: since I'm already here, let's do some basic styling 2025-09-10 02:25:02 +02:00
fbc6cd59bc fix: multiline alt text no longer flows into itself 2025-09-10 02:10:38 +02:00
Weblate
873048de2e Translated using Weblate (Japanese (ja_EASY))
Currently translated at 72.0% (758 of 1052 strings)

Co-authored-by: Deleted User <noreply+21@weblate.org>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/ja_EASY/
Translation: Pleroma fe/pleroma-fe
2025-09-07 01:23:49 +00:00
Weblate
7a4e2a8644 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1052 of 1052 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
2025-09-07 01:23:49 +00:00
Weblate
a1d92ffd86 Translated using Weblate (German)
Currently translated at 98.5% (1037 of 1052 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/de/
Translation: Pleroma fe/pleroma-fe
2025-09-07 01:23:49 +00:00
Oneric
b60f42b959 woodpecker: fix obsolete secrets usage
The existing definition now only errors out
2025-09-07 00:00:00 +00:00
55dff3a9bd Merge pull request 'Fix sensitive by default option' (#443) from norm/akkoma-fe:fix-sensitive into develop
Reviewed-on: AkkomaGang/akkoma-fe#443
Reviewed-by: Oneric <oneric@noreply.akkoma>
2025-09-07 00:50:15 +00:00
9ef8effeed Clarify the sensitiveIfSUbject setting description 2025-09-06 08:31:26 -04:00
9c15db16a6 Fix sensitive by default option
Fixes AkkomaGang/akkoma-fe#442
2025-09-03 20:54:12 -04:00
Weblate
674a816453 Merge branch 'origin/develop' into Weblate. 2025-08-04 00:27:08 +00:00
Weblate
be2207fa42 Translated using Weblate (French)
Currently translated at 100.0% (1051 of 1051 strings)

Co-authored-by: Thomate <thomas@burdick.fr>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/fr/
Translation: Pleroma fe/pleroma-fe
2025-07-21 13:47:34 +00:00
Weblate
3f3ea32f81 Translated using Weblate (Greek)
Currently translated at 18.3% (193 of 1051 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
2025-07-21 13:47:34 +00:00
Weblate
abc6b299e0 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1051 of 1051 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
2025-07-21 13:47:34 +00:00
Weblate
b8b18c67b1 Translated using Weblate (Turkish)
Currently translated at 19.6% (207 of 1051 strings)

Co-authored-by: Hasan Yıldız <hasanyildiz0@yaani.com>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/pleroma-fe/tr/
Translation: Pleroma fe/pleroma-fe
2025-07-21 13:47:34 +00:00
4cf4b5e2d0 Merge pull request 'Support selectable visibility of repeats' (#440) from Oneric/akkoma-fe:boost-scopes into develop
Reviewed-on: AkkomaGang/akkoma-fe#440
2025-06-10 18:37:59 +00:00
d617a9596a Add support selectable visibility of repeat
Co-authored-by: Oneric <oneric@oneric.stub>
2025-05-18 22:52:55 +02:00
Oneric
4734e9668d refactor: extract scope logic into shared module 2025-05-18 22:52:55 +02:00
9787f43343 Merge pull request 'Check for canvas extract permission when initializing favicon service' (#432) from mkljczk/akkoma-fe:check-canvas-extract-permission into develop
Reviewed-on: AkkomaGang/akkoma-fe#432
2025-05-09 18:34:53 +00:00
61bdedc82f Merge pull request 'remove some jank in emoji reacts component' (#435) from Riedler/akkoma-fe:fix-reacts into develop
Reviewed-on: AkkomaGang/akkoma-fe#435
2025-05-09 18:30:07 +00:00
a4eddc7f1c Merge pull request 'polls: base fractions on voters for multiple choice polls' (#436) from Oneric/akkoma-fe:poll-percentages into develop
Reviewed-on: AkkomaGang/akkoma-fe#436
2025-05-09 18:29:54 +00:00
94c5998593 Apply wordfilters to attachment alt-texts
EDITED to apply review suggestions:
  - short circuit search and immediately return once match found
  - Array.some() instead of for loop
2025-05-05 22:39:43 +02:00
Oneric
851dd263c0 docs/sticker: fix example setup 2025-04-25 00:45:04 +02:00
Oneric
473ba89355 polls: base fractions on voters for multiple choice polls
This allows discerning how many voters agreed
with an option and aligns with other clients.
However, a backend bug makes this impossible for
remote multiple choice polls, so retain current
behaviour for anything affected.
2025-04-04 19:27:30 +02:00
4ce8ffcec1 fix: shrink unicode emojis in reactions slightly
some large ones exceeded container boundaries before
2025-03-26 09:09:56 +01:00
e62b154228 fix: uniform height sizing and layouting 2025-03-26 07:39:54 +01:00
e87a9ced61 fix: no more emojis bleeding into button borders 2025-03-26 07:12:45 +01:00
7245775b27 fix: picked reactions should be positioned identically 2025-03-26 06:56:32 +01:00
6373c5a05d Check for canvas extract permission when initializing favicon service 2025-03-05 15:02:16 +00:00
2914eaf1ca Revert "reduce gallery size"
This reverts commit 06ba190e2e.
2025-03-01 16:14:55 +00:00
0bf9cb0660 Merge pull request 'Optional widened main column' (#402) from Riedler/akkoma-fe:wide-columns-for-upstream into develop
Reviewed-on: AkkomaGang/akkoma-fe#402
2025-03-01 12:00:33 +00:00
65cb3b95e0 Merge pull request 'Use FEP-c16b: Formatting MFM functions' (#410) from ilja/akkoma-fe:use_fep-c16b_formatting_mfm_functions into develop
Reviewed-on: AkkomaGang/akkoma-fe#410
2025-02-27 12:04:41 +00:00
f15b94d566 made widenTimeline false by default 2025-02-07 03:50:44 +01:00
06ba190e2e reduce gallery size 2025-02-07 03:49:57 +01:00
2086522d64 Merge pull request '(arguably) improved layouting of user profile page' (#403) from Riedler/akkoma-fe:user-profile-changes into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
d3fa5cfad0 Merge pull request 'post_status_form: enable sync flush for watcher' (#414) from novenary/akkoma-fe:sticky-drafts into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
6b7c8f0def Merge pull request 'Allow using custom source URLs' (#421) from Oneric/akkoma-fe:custom-source into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
ad6bb47003 Merge pull request 'Add visual feedback when clicking translate' (#423) from ilja/akkoma-fe:provide_visual_feedback_when_clicking_translate_button into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
ilja space
868c6e41ac Improve readability for MFM styles code
The code to turn mdm-data-* attributes into a value for the style attribute is complex.
I wrapped it in it's own function now for better code readability.
A comment was already provided with what the code intents to do and why, this information has also been moved
to this function.
2024-12-01 12:24:23 +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
ilja space
177d96f977 Improve how scaling is done
During code review a much better way was pointed out to do the emoji scaling, by using `em`.

*key uses 2em for emoji, which is smaller than Akkoma has. I now kept the 38px for Akkoma,
but when "zoom" (ie x2, x3, x4, tada) happens, we set to 2em and zoom from there.
2024-11-01 14:25:22 +01:00
Oneric
42ba77ebf4 Allow using custom source URLs 2024-10-26 16:32:14 +02:00
4a50b1273d Merge pull request 'fix panel z-index conflicting with heading popover' (#422) from tea/akkoma-fe:fix/panel-z-index into develop
Reviewed-on: AkkomaGang/akkoma-fe#422
2024-10-26 03:42:48 +00:00
c76dc6d79e Merge pull request 'Fix redirect on logout' (#420) from Oneric/akkoma-fe:logout-redirect into develop
Reviewed-on: AkkomaGang/akkoma-fe#420
2024-10-26 03:42:23 +00:00
cb4c581cde Merge pull request 'Add proper autocomplete prop for TOTP login field' (#424) from tudbut/akkoma-fe:develop into develop
Reviewed-on: AkkomaGang/akkoma-fe#424
2024-10-26 03:41:15 +00:00
ilja
7f08fe9dc4 Revert "Remove pre-wrap from status_body"
This reverts commit 01164fc520.
2024-10-20 11:54:55 +02: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: #413
2024-10-15 00:16:45 +03:00
ilja
01164fc520 Remove pre-wrap from status_body
For some strange reason, after a mention a quote would be double as high as it should.

Removing this "pre-wrap" seems to fix this. I'm not sure what it was exactly for, but I don't see anything break.

The code blocks now don't wrap any more, but show a scroll bar, which imo is better for a code block.
2024-10-12 18:27:14 +02:00
ilja
fa058ca093 Fix spacing between paragraph and blockquote
Blockquotes are blocks, so not wrapped in an extra p-tag.

In statusses this gave an unfortunate result that the margins were different.
A p-tag has a bottom margin of 1em. Blockquotes had 0.2em top and bottom.

So under a paragraph there was 1em space, but under the blockquote, there was only 0.2em space.

The last p-tag has 0 margin at the bottom.

This commit basically does the same thing for blockquotes now, making it more consistent.

One difference is that the blockquote has a left margin of 0.2em because a little "jump"
in makes it look a bit better imo.
2024-10-12 17:29:35 +02:00
ilja
a9367d444a Make text faint for blockquotes
In code review it was decided that faint text for blockquotes should be used.
I copied how it was done in other places to faint text.

When making a theme for *oma-fe, there's a check for how readable things remain.
I'm unsure how that exactly works, but timestamps for a status is also faint,
so by using the same way of doing this, this should also be taken into account
for the theming engine.
2024-10-12 14:39:33 +02:00
tea
35cf3327c8 fix panel z-index conflicting with heading popover
resolves #342
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
f391cf70a4 Merge pull request 'README: Add line to install Node.js' (#409) from ilja/akkoma-fe:README_add_to_install_node into develop
Reviewed-on: AkkomaGang/akkoma-fe#409
2024-08-25 09:09:35 +00:00
fa8fde2ab1 Merge pull request 'Upgrade vue packages' (#411) from Oneric/akkoma-fe:vue-mathml into develop
Reviewed-on: AkkomaGang/akkoma-fe#411
2024-08-25 09:08:04 +00:00
1f2c96a485 Merge pull request 'Fix setting restore from file' (#406) from Oneric/akkoma-fe:fix-file-restore into develop
Reviewed-on: AkkomaGang/akkoma-fe#406
2024-08-25 09:07:18 +00:00
ilja
51a51fe6b8 Improve style for quoted text in RichContent
Previously quoted text (e.g. in Markdown `> Some text`) was italic
When two different quotes were made, there was no destinction between the two, making it look like one quote
This is confusing

Now we have a vertical line in front of the quote
When two different pieces of text are quoted, it is now clear because the lines are separated
This vertical line is a typical way of visualising quoted text, so it should be easy to understand what it is
2024-08-21 17:57:40 +02:00
ilja
25681cf5f6 Don't require # in the data-mfm-color attribute
For colour in MFM attributes, we expected a `#`, but that's apparently wrong. The BE
translates the `color` attribute in `$[fg.color=000 text]` into `data-mfm-color=000`.
But for the SCSS to work, we need to put it in the style attribute as `--mfm-color: #000`.

Generally we just add the attribute value as-is in the `style` attribute, but now we
have a special exception for color so we add a `#` before the value.
2024-08-18 15:48:22 +02:00
Oneric
6c178aa257 Upgrade vue packages
Bumping past vue 3.4.0 guarantees MathML support for FEP-dc88.

Related to: AkkomaGang/akkoma#642
2024-08-17 18:01:59 +02:00
ilja
6666a273a4 MFM only use sanitised data-* attribute values
We take the value from a data-* attribute and then add this to the style attribute.
This will probably be OK in most cases, but just to be sure, we check for "weird" characters first.
For now we only allow letters, numbers, dot, hash, and plus and minus sign, because those are the ones I currently know of who are used in MFM.
The data-* attribute remains because it was already considered proper HTML as-is.
2024-08-11 18:11:03 +02:00
ilja
3210873d7f MFM make all supported tags suggested
When typing MFM, a sugestor drop-down appears so you can see and/or choose what MFM function to use
The new MFM functions we support have now also been added
2024-08-10 13:55:52 +02:00
ilja
f5f9949253 Fix mfm-position and mfm-scale
The `span`'s needed an inline-block for the transform to wrok
I also added an `overflow: hidden;` because these functions can make the text go beyond the borders of the StatusBody
With `overflow: hidden;`, it won't show outside of the borders
2024-08-10 13:13:47 +02:00
ilja
ba4ae5badb Fix MFM functions x2, x3, and x4
These now work for the new, FEP-c16b compliant, representation
Nesting also works

It already worked for text and "normal" emoji, but now it also works for custom emoji
2024-08-10 12:45:37 +02:00
ilja
56a59e1b55 fill in data-mfm- variables
Things like `speed=0.1s` now work

I also noticed a class was set on StatusBody, but we don't use it, we use StatusContent.
Therefor I removed it now.

We do still pass the setting through StatusBody to RichContent bc it's used there to decide to not show greentext for arrows when MFM was used.
Note that while this setting still works
* You have to refresh the page to see it working (was already like this, so I didn't touch it here)
* It explicitly checks for content type. If womeone provides MFM-like HTML, then it will still show as greentext if that option is enabled
  I think it's a bit inconsistent, but otoh, the inconsistency to me seems more that we ignore the greentext option for one input type specifically

I do still notice generall bugs with MFM.
* Position doesn't seem to work, neither does scale.
* There also seems to be a regression where custom emojis don't become larger any more with e.g. `$[x2 :hehe: ]`

I don't assume the regression is made in this commit, so I add this already. The rest needs to be fixed before merging.
2024-08-05 17:23:15 +02:00
ilja
3065416c93 Make new SCSS work for non-variables
The SCSS that we took from Foundkey in a previous commit, is now working
The settings for disabling MFM or only show animation on hover are working
The previous representation also works and it's clearly marked in the code what is legacy
All the MFM SCSS is now located in one file specifically for MFM, ./src/components/status_content/mfm.scss

This is only SCSS:
* The variables who are provided as data-attributes are not working yet
* `sparkle` also doesn't work
2024-08-04 19:10:25 +02:00
94141dcb3c Message from commiter: Add Foundkey MFM stylesheet
This is part of a bigger work to fix MFM in Akkoma
See <AkkomaGang/akkoma#381>

Here we add the MFM stylesheet as it is used by Foundkey
See <b22e627089>

Foundkey uses MFM and both the Founkey and Akkoma projects and communities, have historically been closely related
As such it makes sense to start with feature-parity with Foundkey

This commit only adds the stylesheet so that correct attribution is given
Properly integrating and making it work will happen in later commits
2024-08-04 17:55:32 +02:00
ilja
92e278d406 Move MFM SCSS to separate SCSS
MFM was defined in three places.
There was ./src/components/status_body/status_body.scss => I moved this to ./src/components/status_content/mfm.scss
There was ./src/components/status_content/status_content.vue => I moved this to ./src/components/status_content/mfm.scss
There's ./static/mfm.css => I kept this as-is

./src/components/status_content/mfm.scss is now being loaded in ./src/components/status_content/status_content.vue

I added a comment in both ./src/components/status_content/mfm.scss and ./static/mfm.css referencing each other

Note that this is just a first step in an overhoal of how MFM is handled. It seemed easier to do this as a first step and then build further on that.
2024-08-04 17:44:21 +02: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
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: AkkomaGang/akkoma-fe#405
2024-07-06 01:59:42 +02:00
e955eb4503 oops, unfucked username placement 2024-07-03 18:58:50 +02:00
c39d9fa64b fixed stuff overflowing in user popup e.g. in notifs 2024-07-03 18:30:51 +02:00
a74a631793 stopped user handle from overflowing from its boundaries in user card 2024-07-03 17:45:40 +02:00
2e83ccefdc disabled "compact user info" setting in mobile layout 2024-07-03 17:35:13 +02:00
cf11b2523e disabled compact user card in mobile layout 2024-07-03 17:26:09 +02:00
8765491399 Do not try to destructure when we don't need to 2024-06-27 02:58:52 +01:00
85001814a2 added setting for user info compactness 2024-06-26 18:09:13 +02:00
c902219997 added setting to switch between center and left-aligned user bio 2024-06-26 17:20:50 +02:00
2e2e87db75 expand underlay to screen edges when TL is widened 2024-06-26 16:43:32 +02:00
b2af067fd3 reverted visual changes to underlay 2024-06-26 16:39:04 +02:00
4211e05a75 Merge pull request 'status component: Fix repeater name overflowing' (#383) from yukijoou/akkoma-fe:fix-status-usernames into develop
Reviewed-on: AkkomaGang/akkoma-fe#383
2024-06-25 21:34:15 +00:00
a3251a1ba6 Merge remote-tracking branch 'origin/translations' into develop 2024-06-23 03:03:40 +01:00
e5608f4009 remove ora as a dep 2024-06-23 03:03:11 +01:00
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
24b9e350e2 Merge pull request 'added minimum space to empty timeline' (#400) from Riedler/akkoma-fe:empty-tl-minspace into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
8489f6d5ae Merge pull request 'visually fuse CW line and post body textarea' (#401) from Riedler/akkoma-fe:fuse-cw-to-post-body into develop
Reviewed-on: AkkomaGang/akkoma-fe#401
2024-06-17 21:40:26 +00:00
754cd2fa57 slightly adjusted edit button spacing 2024-06-16 17:15:04 +02:00
31055fb4f2 removed min-width statements that were messing up my layouts 2024-06-16 17:14:59 +02:00
918b0e3770 stopped username from wrapping… 2024-06-16 17:14:14 +02:00
88aae1706a oops, removed unneeded spacing 2024-06-16 17:14:08 +02:00
3d2a8a3ca2 left-aligned bio text
why the fuck was it centered in the first place?!?
2024-06-16 17:14:03 +02:00
a24fff5d5b moved user stats to between user info and user actions 2024-06-16 17:14:00 +02:00
4abddf5e6a made wide column layout optional 2024-06-16 16:37:33 +02:00
1b4df9e79d reverted audio attachments to 4:1 aspect ratio 2024-06-16 16:37:30 +02:00
45fe334cd7 fixed sizing issues with attachments in some non-status containers 2024-06-16 16:37:26 +02:00
dd32a33d59 fixed media attachment heights 2024-06-16 16:37:22 +02:00
74b651a3a2 made attached images max size scale with font size
meta-comment: eliminated corner-case weirdness by replaced cursed CSS with slightly less cursed CSS
2024-06-16 16:37:07 +02:00
21fe7d76d3 made columns use more space, fixed minor bug 2024-06-16 16:35:46 +02:00
b2cab6d088 only flatten top of post body textarea if subject line is visible 2024-06-16 16:26:44 +02:00
3ebaba6fa7 smushed subject line and post body together, kinda 2024-06-16 16:14:16 +02:00
f1058567b9 also set min height for other lists (e.g. follow requests), not just the timeline 2024-06-16 16:12:15 +02:00
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
c68595345f Merge pull request 'ANNOYING dependency update' (#397) from dep-update into develop
Reviewed-on: AkkomaGang/akkoma-fe#397
2024-06-15 12:41:23 +00:00
a5d4b0a68c Merge branch 'develop' into dep-update 2024-06-15 13:38:40 +01:00
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
97966045cb Merge pull request 'make status form selectors justified properly' (#398) from rat/akkoma-fe:status-form-selector into develop
Reviewed-on: AkkomaGang/akkoma-fe#398
2024-06-15 12:33:38 +00:00
rat
aad023c8a0 make status form selectors justified properly 2024-06-10 17:20:51 -07:00
c952b2335c correct eslint plugin 2024-05-29 04:04:56 +01:00
0baf31f498 correct package.json lint task 2024-05-29 04:01:29 +01:00
8fa24d0c40 migrate to eslint 9 syntax 2024-05-29 03:59:37 +01:00
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
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
7789c5def6 Update .woodpecker.yml 2024-05-28 21:31:46 +00:00
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
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
0b437ab6fd remove line left over in conflict 2024-05-28 04:15:35 +01:00
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
8964dce609 Merge pull request 'Make animated emojis in reactions pause' (#378) from sarayalth/akkoma-fe:pause-animated-reaction into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
1a49a1b3ac Merge pull request 'Expand Unicode emoji map' (#385) from Oneric/akkoma-fe:emoji_update into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
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
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
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
70275684bf Merge pull request 'Fix posting for "special" interface languages' (#377) from Oneric/akkoma-fe:post-language-specialcodes into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
29ff2ef455 Merge pull request 'Fix ordering of favourites timeline' (#392) from Oneric/akkoma-fe:favourite-ordering into develop
Reviewed-on: AkkomaGang/akkoma-fe#392
2024-05-28 02:07:15 +00:00
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: AkkomaGang/akkoma-fe#391
2024-05-15 18:47:47 +02:00
cc709394c5 remove unused classes on notifications 2024-05-14 18:09:21 +02:00
57beea6a0d fix: Use label and key for options 2024-05-13 16:13:58 +03: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
042e8c78dc feat: Add "Default post language" option
Refs #386
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
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
ed0b403c33 Merge pull request 'Auto-approve followbacks (frontend part)' (#365) from Oneric/akkoma-fe:followbacks-fe into develop
Reviewed-on: AkkomaGang/akkoma-fe#365
2024-02-20 16:24:37 +00:00
0f842b300b Merge pull request 'Display profile background of other users' (#371) from Oneric/akkoma-fe:profile-backgrounds into develop
Reviewed-on: AkkomaGang/akkoma-fe#371
2024-02-20 16:20:14 +00:00
865cb6f96a Merge pull request 'Add Indonesian translation' (#366) from leap123/akkoma-fe:leap123-patch-1 into develop
Reviewed-on: AkkomaGang/akkoma-fe#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 AkkomaGang/akkoma#674
2024-02-04 22:19:14 +01:00
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
8dce31d0ad Merge pull request 'Improve UX of subject / Content Warning field' (#362) from hazelnoot/akkoma-fe:develop into develop
Reviewed-on: AkkomaGang/akkoma-fe#362
2023-12-20 18:49:40 +00:00
ea9ad4d600 fix "always show content warning" setting 2023-12-20 12:39:31 -05:00
34e2800f59 add button to toggle the spoiler / CW field 2023-12-16 14:44:26 -05:00
3d65eccf04 use main emoji button for spoiler / CW field 2023-12-16 13:37:59 -05:00
d304be654f Merge pull request 'Update build setup instructions' (#343) from norm/pleroma-fe:update-build-setup into develop
Reviewed-on: AkkomaGang/akkoma-fe#343
2023-12-15 12:24:33 +00:00
aee97fa948 Merge pull request 'Re-added extension checking for still-image' (#346) from Mergan/pleroma-fe:still-image-ultimate into develop
Reviewed-on: AkkomaGang/akkoma-fe#346
2023-12-15 12:24:07 +00:00
7da1687f31 Merge pull request 'Use relative font size and set appropriate overflow behavior' (#355) from xarvos/pleroma-fe:update-css into develop
Reviewed-on: AkkomaGang/akkoma-fe#355
2023-12-15 12:12:28 +00:00
a8f193d4bd Merge pull request 'Stop constant movement of notifications due to changing timestamps' (#353) from Oneric/akkoma-fe:notification-writhing into develop
Reviewed-on: AkkomaGang/akkoma-fe#353
2023-12-15 11:57:47 +00:00
81c82e11bc Merge pull request 'Explicitly set SameSite attribute for cookies' (#352) from Oneric/akkoma-fe:cookie-samesite into develop
Reviewed-on: AkkomaGang/akkoma-fe#352
2023-12-15 11:54:15 +00:00
00cadce5b4 Merge pull request 'Format dates, times with window.navigator.language instead of UI i18n locale' (#354) from smitten/akkoma-fe:date-locale-fix-cherrypick into develop
Reviewed-on: AkkomaGang/akkoma-fe#354
2023-12-15 11:52:59 +00:00
40a08f279b Merge pull request 'Drop broken "@ symbol as icon" setting' (#359) from Oneric/akkoma-fe:at-icon into develop
Reviewed-on: AkkomaGang/akkoma-fe#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
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
deaef1d0b9
Use relative unit for font size 2023-11-05 09:21:01 +07:00
1b28ec3b72
Match UI i18n configuration to browser locales 2023-11-01 23:10:57 -04:00
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
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
1de62fffcd
Update config.example.json link and example domain 2023-10-06 04:52:04 -04:00
306cea04a1
Use corepack in build instructions 2023-10-06 04:51:59 -04:00
d9e1bc4d99 Re-added extension checking for still-image
- Bonus refactoring
2023-10-02 15:29:54 -07:00
243 changed files with 12911 additions and 8098 deletions

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
}
}

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/arm64
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,18 @@ pipeline:
branch:
- develop
- stable
image: node:18
secrets:
- SCW_ACCESS_KEY
- SCW_SECRET_KEY
- SCW_DEFAULT_ORGANIZATION_ID
image: node:20
environment:
SCW_ACCESS_KEY:
from_secret: SCW_ACCESS_KEY
SCW_SECRET_KEY:
from_secret: SCW_SECRET_KEY
SCW_DEFAULT_ORGANIZATION_ID:
from_secret: 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_arm64
- mv scaleway-cli_2.30.0_linux_arm64 scaleway-cli
- chmod +x scaleway-cli
- ./scaleway-cli object config install type=rclone
- zip akkoma-fe.zip -r dist
@ -63,15 +67,17 @@ pipeline:
- stable
environment:
CI: "true"
SCW_ACCESS_KEY:
from_secret: SCW_ACCESS_KEY
SCW_SECRET_KEY:
from_secret: SCW_SECRET_KEY
SCW_DEFAULT_ORGANIZATION_ID:
from_secret: SCW_DEFAULT_ORGANIZATION_ID
image: python:3.10-slim
secrets:
- SCW_ACCESS_KEY
- SCW_SECRET_KEY
- 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_arm64
- mv scaleway-cli_2.30.0_linux_arm64 scaleway-cli
- chmod +x scaleway-cli
- ./scaleway-cli object config install type=rclone
- cd docs
@ -79,4 +85,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/

View file

@ -5,10 +5,70 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Added
- the search interface now exposes more control knobs:
- post search can now be limited to a single post author
- user search can now be limited to followed users
- resolving remote URLs or WebFinger lookups are now optional
- search can be limited to a particular type for faster responses
- search now allows fetching additional matches past the first page
- further extended MFM support
(ruby, unixtime, fade, border, crop, animation loop count, animation delay and some fixes)
- it is now possible to view poll results without voting
### Fixed
- favourite and repeat indicators no longer occasionally disappear after a successfull interaction
- favourite and repeat indicators no longer wrongly pretend success after a failed interaction
### Changed
- various minor visual styling enhancements
- overly long polls can and are now collapsed analogous to overly long status texts
## 2026.05 (3.19) - 2026-05-04
### Added
- lists UI can now read and set the "exclusive" parameter, allowing members to be removed from the home timeline
- user profiles now have a small gallery for profile media
- alt text of user profile media is now exposed and can be edited in profile settings
- if known, polls now show what promise wrt to vote anonymity was made
### Fixed
- fix error on list creation preventing initial accounts from being actually added
- fix notifications on mobile
- fix attachment display for remotes not federating any MIME type indicators if they still indicate a generic type.
This applies to e.g. bridgy
- fixed some spacing issues after mentions
- MFM statuses now use the same emoji base size as *keys for better compatability
### Changed
- reworked rich content (anything with custom emoji or not pure plaintext) parsing;
there _should_ be no visible changes except fixing obviously broken edgecases
and perhaps more reliable green- and cyantext styling
- the frontend now also applies its own HTML sanitisation instead of relying solely on the backends sanitisation;
this shouldn't cause any visible changes but further hardens against potential future bugs
- various minor visual styling enhancements
## 2026.03 (3.18.0) - 2026-03-14
### REMOVED
- dropped obsolete and buggy dm timeline
### Added
- UI for conversations API, replacing the DM timeline.
Here each thread (conversation) has its own timeline and read markers instead of mixing everything together.
- Boosts now show when and with which visibility they were boosted
- bookmarks are now accessible via the narrow/mobile UI
### Fixed
- fixed saving fallback cop yof settings to local browser storage
- improve image animation detection further
- fix status content parsing for mention and hashtag detection; this could lock the UI until reload
- fix display of nsfw attachment overlays on webkit
## Between 2022.09 (3.2.0) and 2025.12 (3.17.0)
A whole lot of stuff, but we forgot to update the changelog besides the one entry below, oopsi
- Implemented remote interaction with statuses
## 2022.09 - 2022-09-10
## 2022.09 (3.2.0) - 2022-09-10
### Added
- Automatic post translations. Must be configured on the backend in order to work.
- Post editing, including a log of previous edits.

View file

@ -1,4 +1,4 @@
# Akkoma-FE
# Akkoma-FE
![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)
@ -8,7 +8,7 @@ This is a fork of Akkoma-FE from the Pleroma project, with support for new Akkom
# 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.
@ -20,9 +20,11 @@ To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/ad
## Build Setup
Make sure you have [Node.js](https://nodejs.org/) installed. You can check `/.woodpecker.yml` for which node version the Akkoma CI currently uses.
``` bash
# install dependencies
npm install -g yarn
npm install -g corepack
yarn
# serve with hot reload at localhost:8080
@ -37,7 +39,7 @@ npm run unit
# 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.
@ -52,4 +54,5 @@ Edit config.json for configuration.
### 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"'
})

View file

@ -15,12 +15,13 @@ put a file that looks like this
```json
{
"myPack": "/static/stickers/myPack"
"myPack": "/static/stickers/myPack/"
}
```
This file is a mapping from name to pack directory location. It says "we have a pack called myPack, look for
it at `/static/stickers/myPack`". You can add as many packs as you like in this manner.
it inside `/static/stickers/myPack`". You can add as many packs as you like in this manner.
Note that a single leading and a trailing slash are **required** to work correctly!
## Creating the pack

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

@ -6,7 +6,6 @@
<title>Akkoma</title>
<link rel="stylesheet" href="/static/font/tiresias.css">
<link rel="stylesheet" href="/static/font/css/lato.css">
<link rel="stylesheet" href="/static/mfm.css">
<link rel="stylesheet" href="/static/custom.css">
<link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
<!--server-generated-meta-->

View file

@ -1,6 +1,6 @@
{
"name": "pleroma_fe",
"version": "3.10.0",
"version": "3.19.0",
"description": "A frontend for Akkoma instances",
"author": "Roger Braun <roger@rogerbraun.net>",
"private": true,
@ -12,123 +12,123 @@
"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",
"dompurify": "^3.3.3",
"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",
"npm": ">= 3.0.0"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View file

@ -6,6 +6,7 @@ import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_pan
import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import ModModal from './components/mod_modal/mod_modal.vue'
import SearchFormModal from './components/search_form_modal/search_form_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue'
@ -35,6 +36,7 @@ export default {
DesktopNav,
SettingsModal,
ModModal,
SearchFormModal,
UserReportingModal,
PostStatusModal,
EditStatusModal,
@ -59,11 +61,17 @@ export default {
{
'-reverse': this.reverseLayout,
'-no-sticky-headers': this.noSticky,
'-has-new-post-button': this.newPostButtonShown
'-has-new-post-button': this.newPostButtonShown,
'-wide-timeline': this.widenTimeline
},
'-' + 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 +79,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 {
@ -88,6 +96,9 @@ export default {
newPostButtonShown () {
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
widenTimeline () {
return this.$store.getters.mergedConfig.widenTimeline
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
editingAvailable () { return this.$store.state.instance.editingAvailable },
layoutType () { return this.$store.state.interface.layoutType },

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
}
@ -172,6 +172,10 @@ nav {
background-color: rgba(0, 0, 0, 0.15);
background-color: var(--underlay, rgba(0, 0, 0, 0.15));
z-index: -1000;
.-wide-timeline & {
margin:0 calc(var(--columnGap) / -2);
}
}
.app-layout {
@ -187,12 +191,17 @@ nav {
grid-template-rows: 1fr;
box-sizing: border-box;
margin: 0 auto;
padding: 0 calc(var(--columnGap) / 2);
align-content: flex-start;
flex-wrap: wrap;
justify-content: center;
min-height: 100vh;
overflow-x: clip;
&.-wide-timeline {
--maxiColumn: minmax(var(--miniColumn), 1fr);
}
.column {
--___columnMargin: var(--columnGap);

View file

@ -62,6 +62,7 @@
<StatusHistoryModal v-if="editingAvailable" />
<SettingsModal />
<ModModal />
<SearchFormModal />
<GlobalNoticeList />
</div>
</template>

View file

@ -12,7 +12,6 @@ import routes from './routes'
import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import { applyTheme } from '../services/style_setter/style_setter.js'
@ -183,6 +182,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'])
}
@ -247,17 +252,6 @@ const getStickers = async ({ store }) => {
}
}
const getAppSecret = async ({ store }) => {
const { state, commit } = store
const { oauth, instance } = state
return getOrCreateApp({ ...oauth, instance: instance.server, commit })
.then((app) => getClientToken({ ...app, instance: instance.server }))
.then((token) => {
commit('setAppToken', token.access_token)
commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
})
}
const resolveStaffAccounts = ({ store, accounts }) => {
const nicknames = accounts.map(uri => uri.split('/').pop())
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
@ -345,7 +339,7 @@ const setConfig = async ({ store }) => {
const apiConfig = configInfos[0]
const staticConfig = configInfos[1]
await setSettings({ store, apiConfig, staticConfig }).then(getAppSecret({ store }))
await setSettings({ store, apiConfig, staticConfig })
}
const checkOAuthToken = async ({ store }) => {

View file

@ -1,12 +1,14 @@
import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import DMConvTimeline from 'components/dm_conv_timeline/dm_conv_timeline.vue'
import DMConvList from 'components/dm_conv_list/dm_conv_list.vue'
import DMConvRecipients from 'components/dm_conv_recipients/dm_conv_recipients.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
import ConversationPage from 'components/conversation-page/conversation-page.vue'
import Interactions from 'components/interactions/interactions.vue'
import DMs from 'components/dm_timeline/dm_timeline.vue'
import UserProfile from 'components/user_profile/user_profile.vue'
import Search from 'components/search/search.vue'
import Registration from 'components/registration/registration.vue'
@ -47,6 +49,9 @@ export default (store) => {
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline },
{ name: 'bubble-timeline', path: '/main/bubble', component: BubbleTimeline },
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/main/conversations', component: DMConvList, beforeEnter: validateAuthenticatedRoute },
{ name: 'dm_conversation', path: '/main/conversations/:id', component: DMConvTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'dm-conversation-recipients', path: '/main/conversations/:id/recipients', component: DMConvRecipients },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
@ -62,7 +67,6 @@ export default (store) => {
},
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile, meta: { dontScroll: true } },
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
{ name: 'registration', path: '/registration', component: Registration },
{ name: 'registration-request-sent', path: '/registration-request-sent', component: RegistrationRequestSent },
{ name: 'awaiting-email-confirmation', path: '/awaiting-email-confirmation', component: AwaitingEmailConfirmation },
@ -72,7 +76,14 @@ export default (store) => {
{ name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute },
{ name: 'login', path: '/login', component: AuthForm },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'search', path: '/search', component: Search, props: (route) => ({
query: route.query.query,
type: route.query.type,
resolve: route.query.resolve,
account_id: route.query.account_id,
following: route.query.following,
})
},
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About },
{ name: 'lists', path: '/lists', component: Lists },

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

@ -54,7 +54,7 @@ const Attachment = {
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
preloadImage: this.$store.getters.mergedConfig.preloadImage,
loading: false,
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
img: fileTypeService.fileType(this.attachment) === 'image' && document.createElement('img'),
modalOpen: false,
showHidden: false,
flashLoaded: false,
@ -105,7 +105,7 @@ const Attachment = {
return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
},
type () {
return fileTypeService.fileType(this.attachment.mimetype)
return fileTypeService.fileType(this.attachment)
},
hidden () {
return this.nsfw && this.hideNsfwLocal && !this.showHidden

View file

@ -19,6 +19,17 @@
height: 200px;
position: relative;
overflow: hidden;
align-content: center;
.status-popover & {
height: 200px;
}
}
&.-nsfw-placeholder {
.attachment-wrapper {
align-content: unset;
}
}
.description-container {
@ -37,7 +48,7 @@
white-space: pre-line;
word-break: break-word;
text-overflow: ellipsis;
overflow: scroll;
overflow: auto;
}
&.-static {
@ -115,6 +126,24 @@
align-items: center;
justify-content: center;
padding-top: 0.5em;
p {
line-height: 1.5;
padding: 0 0.5em;
white-space: pre-line;
text-align: center;
max-height: 200px;
overflow-y: auto;
scrollbar-color: var(--border) #0000;
width: 100%;
box-sizing: border-box;
.status-popover & {
text-overflow: ellipsis;
overflow: hidden;
height: 1lh;
}
}
}

View file

@ -246,8 +246,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

@ -22,12 +22,12 @@
<script>
export default {
emits: ['update:modelValue'],
props: [
'modelValue',
'indeterminate',
'disabled'
]
],
emits: ['update:modelValue']
}
</script>
@ -69,6 +69,8 @@ export default {
}
&.disabled {
cursor: not-allowed;
.checkbox-indicator::before,
.label {
opacity: .5;

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({
@ -414,6 +414,14 @@ const conversation = {
},
toggleExpanded () {
this.expanded = !this.expanded
const navHeight = document.getElementById("nav").offsetHeight
const headingHeight = document.getElementsByClassName("timeline-heading")[0].offsetHeight
document.documentElement.style.setProperty("--timeline-scroll-margin-top", `${navHeight + headingHeight}px`)
this.$nextTick(() => {
if (!this.expanded) {
this.$el.scrollIntoView({ block: 'nearest' })
}
})
},
getConversationId (statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId]

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>
@ -278,5 +278,7 @@
&.-expanded.status-fadein {
margin: calc(var(--status-margin, $status-margin) / 2);
}
scroll-margin-block-start: var(--timeline-scroll-margin-top);
}
</style>

View file

@ -1,4 +1,3 @@
import SearchBar from 'components/search_bar/search_bar.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -47,11 +46,9 @@ library.add(
export default {
components: {
SearchBar,
ConfirmModal
},
data: () => ({
searchBarHidden: true,
supportsMask: window.CSS && window.CSS.supports && (
window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') ||
@ -78,7 +75,7 @@ export default {
logoBgStyle () {
return Object.assign({
'margin': `${this.$store.state.instance.logoMargin} 0`,
opacity: this.searchBarHidden ? 1 : 0
opacity: 1
}, this.enableMask ? {} : {
'background-color': this.enableMask ? '' : 'transparent'
})
@ -120,8 +117,8 @@ export default {
scrollToTop () {
window.scrollTo(0, 0)
},
onSearchBarToggled (hidden) {
this.searchBarHidden = hidden
openSearchModal () {
this.$store.dispatch('openSearchModal')
},
openSettingsModal () {
this.$store.dispatch('openSettingsModal')

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
@ -96,15 +96,35 @@
>
</router-link>
<div class="item right actions">
<search-bar
<button
v-if="currentUser || !privateMode"
@toggled="onSearchBarToggled"
@click.stop
/>
class="button-unstyled nav-icon"
:title="$t('nav.search')"
type="button"
@click.stop="openSearchModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="search"
/>
</button>
<div
v-if="(currentUser || !privateMode) && showNavShortcuts"
class="nav-items right"
>
<router-link
v-if="currentUser"
class="nav-icon"
:to="{ name: 'dms' }"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="envelope"
:title="$t('nav.dm_conversations')"
/>
</router-link>
<router-link
v-if="currentUser"
class="nav-icon"

View file

@ -0,0 +1,80 @@
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import Status from '../status/status.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
const DMConvCard = {
components: {
ConfirmModal,
Status,
UserAvatar
},
props: {
conversation: {
type: Object,
required: true
},
compact: {
type: Boolean,
default: true
},
showLastStatus: {
type: Boolean,
default: true
},
showFullControls: {
type: Boolean,
default: false
}
},
emits: ['deleted'],
data () {
return {
showingDeleteConfirmDialogue: false
}
},
computed: {
shouldConfirmDelete() {
return this.$store.getters.mergedConfig.modalOnDeleteDMConversation;
},
membersTruncated() {
// XXX: this should ideally adapt to panel width
const maxLen = 11
const full = this.conversation.accounts
const truncated = full.length > maxLen
const truncList = truncated ? full.slice(0, maxLen) : full
return {
truncated: truncated,
users: truncList
}
},
last_status_text() {
return this.conversation.last_status?.content
}
},
methods: {
markRead() {
this.$store.dispatch('markDMConversationAsRead', { id: this.conversation.id })
},
showDeleteConfirmModal() {
this.showingDeleteConfirmDialogue = true
},
hideDeleteConfirmModal() {
this.showingDeleteConfirmDialogue = false
},
deleteConversation() {
if (this.shouldConfirmDelete) {
this.showDeleteConfirmModal()
} else {
this.doDeleteConversation()
}
},
doDeleteConversation() {
this.$store.dispatch('deleteDMConversation', { id: this.conversation.id })
this.hideDeleteConfirmModal()
this.$emit('deleted')
}
}
}
export default DMConvCard

View file

@ -0,0 +1,134 @@
<template>
<div class="dm-conv-card">
<router-link
:to="{ name: 'dm_conversation', params: {id: conversation.id }}"
>
<div class="heading">
<div class="title-bar">
<div class="title-bar-left">
<div
v-if="conversation.unread"
class="unread"
>
<span
class="badge badge-notification"
role="figure"
:title="$t('dm_conv.unread_msg')"
:alt="$t('dm_conv.unread_msg')"
>
!
</span>
<button
class="button-unstyled"
:title="$t('dm_conv.mark_single_read_tooltip')"
@click.stop.prevent="markRead()"
>
<FAIcon
icon="check"
class="fa-scale-110 fa-old-padding dm-conv-mark-read"
/>
</button>
&nbsp;
</div>
<h4>{{ $t('dm_conv.default_name', {id: conversation.id}) }}</h4>
</div>
<div class="title-bar-right">
<button
class="button-unstyled button-delete"
:title="$t('dm_conv.delete_tooltip')"
@click.stop.prevent="deleteConversation()"
>
<FAIcon
icon="trash-alt"
class="fa-scale-110 fa-old-padding dm-conv-delete"
/>
</button>
</div>
</div>
</div>
<div class="members">
<UserAvatar
v-for="user in membersTruncated.users"
:key="user.id"
:user="user"
:compact="compact"
/>
<div
v-if="membersTruncated.truncated"
class="ellipsis"
>
...
</div>
</div>
</router-link>
<div
v-if="showLastStatus"
class="last-message"
>
<div class="last-message-title">
{{ $t('dm_conv.last_message_title') }}:
</div>
<Status
:statusoid="conversation.last_status"
:compact="true"
:is-preview="true"
/>
</div>
<div
v-if="showFullControls"
class="controls"
>
<button
class="btn button-default"
:title="$t('dm_conv.recipients_edit_mode_button_tooltip')"
@click.once="$router.push({ name: 'dm-conversation-recipients', params: { id: conversation.id }})"
>
{{ $t('dm_conv.recipients_edit_mode_button') }}
</button>
</div>
<teleport to="#modal">
<confirm-modal
v-if="showingDeleteConfirmDialogue"
:title="$t('dm_conv.delete_confirm_title')"
:confirm-text="$t('dm_conv.delete_confirm_accept_button')"
:cancel-text="$t('dm_conv.delete_confirm_cancel_button')"
@accepted="doDeleteConversation"
@cancelled="hideDeleteConfirmModal"
>
{{ $t('dm_conv.delete_confirm', { identifier: conversation.id }) }}
</confirm-modal>
</teleport>
</div>
</template>
<script src="./dm_conv_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.dm-conv-card {
.heading, .title-bar, .title-bar-left, .members {
display: flex;
flex-wrap: nowrap;
overflow-x: hidden;
}
.title-bar {
width: 100%;
justify-content: space-between;
}
.controls {
text-align: center;
}
.members {
padding: 6px 0;
}
.last-message-title {
font-style: italic;
color: var(--faint);
}
}
</style>

View file

@ -0,0 +1,33 @@
import DMConvCard from '../dm_conv_card/dm_conv_card.vue'
import List from '../list/list.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more'
const PaginatedDMConvList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchDMConversationList'),
select: (props, $store) => $store.state.dmConversations.allDMConversations || [],
destroy: (props, $store) => $store.dispatch('clearDMConversations'),
childPropName: 'items',
additionalPropNames: []
})(List)
const DMConvList = {
components: {
PaginatedDMConvList,
DMConvCard
},
data () {
return {}
},
computed: {
conversations() {
return this.$store.state.dmConversations.allDMConversations
}
},
methods: {
markAllRead() {
this.$store.dispatch('markAllDMConversationsAsRead')
}
}
}
export default DMConvList

View file

@ -0,0 +1,47 @@
<template>
<div class="settings panel panel-default dm-conv-panel">
<div class="panel-heading">
<div class="title">
{{ $t('nav.dm_conv_list') }}
</div>
</div>
<div class="panel-controls">
<button
class="btn button-default mark-all-read-button"
@click="markAllRead()"
>
{{ $t('dm_conv.mark_all_read_button') }}
</button>
</div>
<div class="panel-body dm-conv-list">
<PaginatedDMConvList>
<template #item="{item}">
<DMConvCard :conversation="item" />
</template>
</PaginatedDMConvList>
</div>
</div>
</template>
<script src="./dm_conv_list.js"></script>
<style lang="scss">
.dm-conv-panel {
.dm-conv-list {
margin: 0 1em;
.dm-conv-card {
margin: 2.5em 0;
}
}
.panel-controls {
margin-top: 0.5em;
text-align: center;
}
.mark-all-read-button {
display: inline-block;
}
}
</style>

View file

@ -0,0 +1,57 @@
import { mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ListUserSearch from '../list_user_search/list_user_search.vue'
const DMConvRecipients = {
data () {
return {
conversationId: null,
conversation: null,
recipients: [],
suggestions: []
}
},
components: {
BasicUserCard,
ListUserSearch
},
computed: {
conversationTitle () {
return this.$i18n.t('dm_conv.default_name', {id: this.conversationId})
},
...mapGetters(['findUser'])
},
methods: {
toggleUser (user) {
if (this.isRecipient(user)) {
this.recipients.filter((r) => r.id !== user.id)
} else {
this.recipients.push(user)
}
},
isRecipient (user) {
return this.recipients.some((r) => r.id == user.id)
},
onResults (results) {
this.suggestions = results.map((id) => this.findUser(id)).filter(user => user)
},
updateRecipients () {
const recipientIds = this.recipients.map((u) => u.id)
this.$store.dispatch('setDMConversationDetails', {id: this.conversationId, recipients: recipientIds })
.then((updateConv) => {
this.conversation = updateConv
this.recipients = updateConv.accounts
})
}
},
created () {
this.conversationId = this.$route.params.id
this.$store.dispatch('fetchDMConversationDetails', { id: this.conversationId })
.then((data) => {
this.conversation = data
this.recipients = data.accounts
})
},
}
export default DMConvRecipients

View file

@ -0,0 +1,82 @@
<template>
<div class="panel-default panel dm-conv-recipients-edit">
<div
ref="header"
class="panel-heading"
>
<div class="title">
{{ $t('dm_conv.recipients_edit_title', {conversation_name: conversationTitle}) }}
</div>
<button
class="btn button-default"
@click="$router.back"
>
{{ $t('nav.back') }}
</button>
</div>
<h4>
{{ $t('dm_conv.recipients_edit_current_title') }}
</h4>
<div class="member-list current-recipients">
<div
v-for="user in recipients"
:key="user.id"
class="member"
>
<BasicUserCard
:user="user"
class="selected"
@click.capture.prevent="toggleUser(user)"
/>
</div>
</div>
<h4>
{{ $t('dm_conv.recipients_edit_add_new_title') }}
</h4>
<ListUserSearch @results="onResults" />
<div class="member-list">
<div
v-for="user in suggestions"
:key="user.id"
class="member"
>
<BasicUserCard
:user="user"
:class="isRecipient(user) ? 'selected' : ''"
@click.capture.prevent="toggleUser(user)"
/>
</div>
</div>
<button
class="btn button-default"
@click="updateRecipients"
>
{{ $t('dm_conv.recipients_save') }}
</button>
</div>
</template>
<script src="./dm_conv_recipients.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.dm-conv-recipients-edit {
.member-list {
padding-bottom: 0.7rem;
}
.current-recipients {
margin-bottom: 1.5em;
}
.basic-user-card:hover,
.basic-user-card.selected {
cursor: pointer;
background-color: var(--selectedPost, $fallback--lightBg);
}
}
</style>

View file

@ -0,0 +1,34 @@
import DMConvCard from '../dm_conv_card/dm_conv_card.vue'
import Timeline from '../timeline/timeline.vue'
const DMConvTimeline = {
data () {
return {
conversationId: null
}
},
components: {
DMConvCard,
Timeline
},
computed: {
conversation () { return this.$store.getters.getDMConversationById(this.conversationId) },
timeline () { return this.$store.state.statuses.timelines.dmConv }
},
methods: {
forceLeave () {
this.$router.push('/')
}
},
created () {
this.conversationId = this.$route.params.id
this.$store.dispatch('fetchDMConversationDetails', { id: this.conversationId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'dmConv', conversationId: this.conversationId })
},
unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'dmConv')
this.$store.commit('clearTimeline', { timeline: 'dmConv' })
}
}
export default DMConvTimeline

View file

@ -0,0 +1,24 @@
<template>
<Timeline
title="$t('dm_conv.page_header')"
:timeline="timeline"
:conversation-id="conversationId"
timeline-name="dmConv"
>
<template
#extraHeading
>
<DMConvCard
v-if="conversation"
:conversation="conversation"
:compact="false"
:show-full-controls="true"
:show-last-status="false"
:link-to-timeline="false"
@deleted="forceLeave"
/>
</template>
</Timeline>
</template>
<script src="./dm_conv_timeline.js"></script>

View file

@ -1,14 +0,0 @@
import Timeline from '../timeline/timeline.vue'
const DMs = {
computed: {
timeline () {
return this.$store.state.statuses.timelines.dms
}
},
components: {
Timeline
}
}
export default DMs

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.dms')"
:timeline="timeline"
:timeline-name="'dms'"
/>
</template>
<script src="./dm_timeline.js"></script>

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

@ -1,3 +1,5 @@
import StillImage from '../still-image/still-image.vue'
const EMOJI_SIZE = 32 + 8
const GROUP_TITLE_HEIGHT = 24
const BUFFER_SIZE = 3 * EMOJI_SIZE
@ -17,6 +19,9 @@ const EmojiGrid = {
resizeObserver: null
}
},
components: {
StillImage
},
mounted () {
const rect = this.$refs.container.getBoundingClientRect()
this.containerWidth = rect.width

View file

@ -34,10 +34,11 @@
@click.stop.prevent="onEmoji(item.emoji)"
>
<span v-if="!item.emoji.imageUrl">{{ item.emoji.replacement }}</span>
<img
<StillImage
v-else
:src="item.emoji.imageUrl"
>
no-stop-gifs="true"
/>
</span>
</template>
</div>

View file

@ -1,5 +1,6 @@
import Completion from '../../services/completion/completion.js'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
import StillImage from '../still-image/still-image.vue'
import { take } from 'lodash'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@ -120,7 +121,8 @@ const EmojiInput = {
}
},
components: {
EmojiPicker
EmojiPicker,
StillImage
},
computed: {
padEmoji () {

View file

@ -20,6 +20,7 @@
ref="picker"
show-keep-open
:class="{ hide: !showPicker }"
:visible="showPicker"
:enable-sticker-picker="enableStickerPicker"
class="emoji-picker-panel"
@emoji="insert"
@ -43,11 +44,15 @@
:class="{ highlighted: index === highlighted }"
@click.stop.prevent="onClick($event, suggestion)"
>
<span v-if="!suggestion.mfm" class="image">
<img
<span
v-if="!suggestion.mfm"
class="image"
>
<StillImage
v-if="suggestion.img"
:src="suggestion.img"
>
no-stop-gifs="true"
/>
<span v-else>{{ suggestion.replacement }}</span>
</span>
<div class="label">

View file

@ -1,4 +1,4 @@
const MFM_TAGS = ['blur', 'bounce', 'flip', 'font', 'jelly', 'jump', 'rainbow', 'rotate', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
const MFM_TAGS = ['bg', 'blur', 'bounce', 'center', 'fg', 'flip', 'font', 'jelly', 'jump', 'position', 'rainbow', 'rotate', 'scale', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
.map(tag => ({ displayText: tag, detailText: '$[' + tag + ' ]', replacement: '$[' + tag + ' ]', mfm: true }))
/**
@ -71,7 +71,7 @@ export const suggestUsers = ({ dispatch, state }) => {
let timeout = null
let cancelUserSearch = null
const userSearch = (query) => dispatch('searchUsers', { query })
const userSearch = (query) => dispatch('searchUsers', { query, resolve: false })
const debounceUserSearch = (query) => {
cancelUserSearch && cancelUserSearch()
return new Promise((resolve, reject) => {
@ -86,19 +86,20 @@ export const suggestUsers = ({ dispatch, state }) => {
}
return async input => {
const noPrefix = input.toLowerCase().substr(1)
if (previousQuery === noPrefix) return suggestions
if (previousQuery === input) return suggestions
suggestions = []
previousQuery = noPrefix
// Fetch more and wait, don't fetch if there's the 2nd @ because
// the backend user search can't deal with it.
// Reference semantics make it so that we get the updated data after
// the await.
if (!noPrefix.includes('@')) {
await debounceUserSearch(noPrefix)
previousQuery = input
// if there are more than two @, its not a valid nick
if (input.match(/@/g)?.length > 2) {
return []
}
// fetch new matching users into our cache
await debounceUserSearch(input)
const noPrefix = input.toLowerCase().substr(1)
const newSuggestions = state.users.users.filter(
user =>
user.screen_name.toLowerCase().startsWith(noPrefix) ||
@ -122,14 +123,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

@ -1,6 +1,7 @@
import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue'
import EmojiGrid from '../emoji_grid/emoji_grid.vue'
import StillImage from '../still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faBoxOpen,
@ -26,12 +27,17 @@ const EmojiPicker = {
required: false,
type: Boolean,
default: false
},
visible: {
required: false,
type: Boolean,
default: true
}
},
data () {
return {
keyword: '',
activeGroup: 'standard',
activeGroup: this.getDefaultGroup(),
showingStickers: false,
keepOpen: false
}
@ -39,7 +45,8 @@ const EmojiPicker = {
components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
Checkbox,
EmojiGrid
EmojiGrid,
StillImage
},
methods: {
debouncedSearch: debounce(function (e) {
@ -82,6 +89,11 @@ const EmojiPicker = {
return list.filter(emoji => {
return (regex.test(emoji.displayText) || (!emoji.imageUrl && emoji.replacement === this.keyword))
})
},
getDefaultGroup () {
if (!this.visible) return null
const recentEmojis = this.$store.getters.recentEmojis
return recentEmojis.length === 0 ? 'standard' : 'recent'
}
},
computed: {
@ -148,6 +160,13 @@ const EmojiPicker = {
stickerPickerEnabled () {
return (this.$store.state.instance.stickers || []).length !== 0 && this.enableStickerPicker
}
},
watch: {
visible (val, oldVal) {
if (val && this.activeGroup === null) {
this.activeGroup = this.getDefaultGroup()
}
}
}
}

View file

@ -18,10 +18,11 @@
@click.prevent="highlight(group.id)"
>
<span v-if="!group.first.imageUrl">{{ group.first.replacement }}</span>
<img
<StillImage
v-else
:src="group.first.imageUrl"
>
no-stop-gifs="true"
/>
</span>
<span
v-if="stickerPickerEnabled"

View file

@ -11,7 +11,7 @@
@click="emojiOnClick(reaction.name, $event)"
@mouseenter="fetchEmojiReactionsByIfMissing()"
>
<span
<template
v-if="reaction.url !== null"
>
<StillImage
@ -19,16 +19,15 @@
:title="reaction.name"
:alt="reaction.name"
class="reaction-emoji"
height="2.55em"
/>
{{ reaction.count }}
</span>
<span v-else>
</template>
<template v-else>
<span class="reaction-emoji unicode-emoji">
{{ reaction.name }}
</span>
<span>{{ reaction.count }}</span>
</span>
</template>
</button>
</UserListPopover>
<a
@ -42,7 +41,7 @@
</div>
</template>
<script src="./emoji_reactions.js" ></script>
<script src="./emoji_reactions.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@ -53,23 +52,26 @@
container-type: inline-size;
}
.unicode-emoji {
font-size: 210%;
}
.emoji-reaction {
padding: 0 0.5em;
padding: 2px 0.5em;
margin-right: 0.5em;
margin-top: 0.5em;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
align-items: end;
.reaction-emoji {
width: auto;
max-width: 96cqw;
height: 2.55em !important;
margin-right: 0.25em;
&.still-image {
height: 2.55em;
}
&.unicode-emoji {
display: inline-block;
font-size: 2.125em; // assuming default line height of 1.2rem and emojis that don't exceed line height
line-height: 2.55rem;
}
}
&:focus {
outline: none;
@ -97,9 +99,9 @@
}
.button-default.picked-reaction {
border: 1px solid var(--accent, $fallback--link);
margin-left: -1px; // offset the border, can't use inset shadows either
margin-right: calc(0.5em - 1px);
&, &:hover {
box-shadow: inset 0 0 0 1px var(--accent, $fallback--link);
}
}
</style>

View file

@ -91,7 +91,7 @@ const ExtraButtons = {
.catch(err => this.$emit('onError', err.error.error))
},
copyLink () {
navigator.clipboard.writeText(this.statusLink)
navigator.clipboard.writeText(this.status.canonical_id)
.then(() => this.$emit('onSuccess'))
.catch(err => this.$emit('onError', err.error.error))
},
@ -155,8 +155,9 @@ const ExtraButtons = {
replyTo: this.status.in_reply_to_status_id,
repliedUser: repliedUser
})
}).then(() => {
this.doDeleteStatus()
})
this.doDeleteStatus()
},
showRedraftStatusConfirmDialog () {
this.showingRedraftDialog = true
@ -187,13 +188,6 @@ const ExtraButtons = {
noTranslationTargetSet () {
return this.$store.getters.mergedConfig.translationLanguage === undefined
},
statusLink () {
if (this.status.is_local) {
return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
} else {
return this.status.external_url
}
},
shouldConfirmDelete () {
return this.$store.getters.mergedConfig.modalOnDelete
},

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

@ -19,15 +19,31 @@ const FavoriteButton = {
},
methods: {
favorite () {
if (!this.status.favorited) {
this.$store.dispatch('favorite', { id: this.status.id })
const undoing = this.status.favorited
let action
if (!undoing) {
action = this.$store.dispatch('favorite', { id: this.status.id })
} else {
this.$store.dispatch('unfavorite', { id: this.status.id })
action = this.$store.dispatch('unfavorite', { id: this.status.id })
}
this.animated = true
setTimeout(() => {
action.then(() => {
this.animated = false
}, 500)
this.$store.dispatch('fetchFavs', this.status.id)
})
.catch((error) => {
this.$store.dispatch('pushGlobalNotice', {
level: 'error',
messageKey: undoing ? 'errors.favorite_undo' : 'errors.favorite',
messageArgs: [error.message ?? error ?? 'unknown'],
timeout: 5000
})
})
.finally(() => {
this.animated = false
})
}
},
computed: {

View file

@ -9,7 +9,7 @@
>
<FAIcon
class="fa-scale-110 fa-old-padding"
:icon="[status.favorited ? 'fas' : 'far', 'star']"
:icon="[(status.favorited || animated) ? 'fas' : 'far', 'star']"
:spin="animated"
/>
</button>
@ -35,7 +35,7 @@
</div>
</template>
<script src="./favorite_button.js" ></script>
<script src="./favorite_button.js"></script>
<style lang="scss">
@import '../../_variables.scss';
@ -61,6 +61,10 @@
animation-duration: 0.6s;
}
.svg-inline--fa.fa-spin {
color: color-mix(in srgb-linear, var(--cOrange, $fallback--cOrange) 70%, currentColor);
}
&:hover .svg-inline--fa,
&.-favorited .svg-inline--fa {
color: $fallback--cOrange;

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

@ -88,10 +88,8 @@ const Gallery = {
set(this.sizes, id, { width, height })
},
rowStyle (row) {
if (row.audio) {
return { 'padding-bottom': '25%' } // fixed reduced height for audio
} else if (!row.minimal && !row.grid) {
return { 'padding-bottom': `${(100 / (row.items.length + 0.6))}%` }
if (!row.audio && !row.minimal && !row.grid) {
return { 'aspect-ratio': `1/${(1 / (row.items.length + 0.6))}` }
}
},
itemStyle (id, row) {

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>
@ -96,9 +96,15 @@
.gallery-row {
position: relative;
height: 0;
width: 100%;
flex-grow: 1;
.Status & {
max-height: 30em;
}
&.-audio {
aspect-ratio: 4/1; // this is terrible, but it's how it was before so I'm not changing it >:(
}
&:not(:first-child) {
margin-top: 0.5em;

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

@ -22,13 +22,17 @@ const ListNew = {
data () {
return {
title: '',
exclusive: false,
userIds: [],
selectedUserIds: []
}
},
created () {
this.$store.dispatch('fetchList', { id: this.id })
.then(() => { this.title = this.findListTitle(this.id) })
.then((list) => {
this.title = list.title
this.exclusive = !!list.exclusive
})
this.$store.dispatch('fetchListAccounts', { id: this.id })
.then(() => {
this.selectedUserIds = this.findListAccounts(this.id)
@ -76,7 +80,7 @@ const ListNew = {
this.userIds = results
},
updateList () {
this.$store.dispatch('setList', { id: this.id, title: this.title })
this.$store.dispatch('setList', { id: this.id, title: this.title, exclusive: this.exclusive })
this.$store.dispatch('setListAccounts', { id: this.id, accountIds: this.selectedUserIds })
this.$router.push({ name: 'list-timeline', params: { id: this.id } })

View file

@ -21,6 +21,17 @@
:placeholder="$t('lists.title')"
>
</div>
<div class="input-wrap">
<input
type="checkbox"
id="list-exclusive-input"
ref="exclusive"
v-model="exclusive"
>
<label for="list-exclusive-input">
{{ $t('lists.exclusive_description') }}
</label>
</div>
<div class="member-list">
<div
v-for="user in selectedUsers"

View file

@ -22,6 +22,7 @@ const ListNew = {
data () {
return {
title: '',
exclusive: false,
userIds: [],
selectedUserIds: []
}
@ -67,7 +68,7 @@ const ListNew = {
createList () {
// the API has two different endpoints for "creating a list with a name"
// and "updating the accounts on the list".
this.$store.dispatch('createList', { title: this.title })
this.$store.dispatch('createList', { title: this.title, exclusive: this.exclusive })
.then((list) => {
this.$store.dispatch('setListAccounts', { id: list.id, accountIds: this.selectedUserIds })
this.$router.push({ name: 'list-timeline', params: { id: list.id } })

View file

@ -21,6 +21,17 @@
:placeholder="$t('lists.title')"
>
</div>
<div class="input-wrap">
<input
type="checkbox"
id="list-exclusive-input"
ref="exclusive"
v-model="exclusive"
>
<label for="list-exclusive-input">
{{ $t('lists.exclusive_description') }}
</label>
</div>
<div class="member-list">
<div

View file

@ -35,7 +35,7 @@ const ListUserSearch = {
this.loading = true
this.userIds = []
this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: this.followingOnly })
.then(data => {
.then(({data}) => {
this.loading = false
this.$emit('results', data.accounts.map(a => a.id))
})

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

@ -43,7 +43,7 @@ const LoginForm = {
}
oauthApi.getOrCreateApp(data)
.then((app) => { oauthApi.login({ ...app, ...data }) })
.then((app) => { oauthApi.login({ ...data, ...app }) })
},
submitPassword () {
const { clientId } = this.oauth

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

@ -67,7 +67,7 @@ const MediaModal = {
},
methods: {
getType (media) {
return fileTypeService.fileType(media.mimetype)
return fileTypeService.fileType(media)
},
hide () {
// HACK: Closing immediately via a touch will cause the click

View file

@ -2,7 +2,7 @@
<Modal
v-if="showing"
class="media-modal-view"
@backdropClicked="hideIfNotSwiped"
@backdrop-clicked="hideIfNotSwiped"
>
<SwipeClick
v-if="type === 'image'"
@ -24,14 +24,15 @@
:min-scale="pinchZoomMinScale"
:reset-to-min-scale-limit="pinchZoomScaleResetLimit"
>
<img
<StillImage
:class="{ loading }"
class="modal-image"
:src="currentMedia.url"
:alt="currentMedia.description"
:title="currentMedia.description"
@load="onImageLoaded"
>
:image-load-handler="onImageLoaded"
no-stop-gifs="true"
/>
</PinchZoom>
</SwipeClick>
<VideoAttachment

View file

@ -42,8 +42,14 @@ const mediaUpload = {
.then((fileData) => {
self.$emit('uploaded', fileData)
self.decreaseUploadCount()
}, (error) => { // eslint-disable-line handle-callback-err
self.$emit('upload-failed', 'default')
}, (error) => {
var msg = typeof error === "string" ? error : error.message
if (msg) {
self.$emit('upload-failed', 'message', [msg])
} else {
self.$emit('upload-failed', 'default')
}
console.warn(`Failed to upload media: ${error}`)
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

@ -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

@ -53,6 +53,9 @@ const NavPanel = {
federating: state => state.instance.federating,
}),
...mapGetters(['unreadAnnouncementCount']),
unreadDMConversationsCount () {
return this.$store.state.users.currentUser?.pleroma?.unread_conversation_count || 0
},
followRequestCount () {
return this.$store.state.users.currentUser.follow_requests_count
}

View file

@ -25,6 +25,24 @@
<TimelineMenuContent class="timelines" />
</div>
</li>
<li v-if="currentUser">
<router-link
class="menu-item"
:to="{ name: 'dms' }"
>
<FAIcon
fixed-width
class="fa-scale-110"
icon="envelope"
/>{{ $t("nav.dm_conversations") }}
<span
v-if="unreadDMConversationsCount > 0"
class="badge badge-notification"
>
{{ unreadDMConversationsCount }}
</span>
</router-link>
</li>
<li v-if="currentUser">
<router-link
class="menu-item"
@ -102,7 +120,7 @@
</div>
</template>
<script src="./nav_panel.js" ></script>
<script src="./nav_panel.js"></script>
<style lang="scss">
@import '../../_variables.scss';

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

@ -1,6 +1,10 @@
import Timeago from 'components/timeago/timeago.vue'
import RichContent from 'components/rich_content/rich_content.jsx'
import { forEach, map } from 'lodash'
import {
faCircleCheck,
faTriangleExclamation
} from '@fortawesome/free-solid-svg-icons'
export default {
name: 'Poll',
@ -12,6 +16,9 @@ export default {
data () {
return {
loading: false,
peekingResults: false,
collapsable: false,
collapsed: false,
choices: []
}
},
@ -21,6 +28,10 @@ export default {
}
this.$store.dispatch('trackPoll', this.pollId)
},
mounted () {
this.collapsable = this.$refs.pollContainer?.scrollHeight > this.$refs.pollContainer?.clientHeight
this.collapsed = this.collapsable
},
unmounted () {
this.$store.dispatch('untrackPoll', this.pollId)
},
@ -44,15 +55,31 @@ export default {
loggedIn () {
return this.$store.state.users.currentUser
},
canVote () {
return !(this.poll.voted || this.expired || !this.loggedIn)
},
showResults () {
return this.poll.voted || this.expired || !this.loggedIn
return !this.canVote || this.peekingResults
},
totalVotesCount () {
return this.poll.votes_count
},
containerClass () {
totalFractionBase () {
// Due to a backend bug, we might not have any voter count info for remote polls
// in this case, fall back to count of votes even for multiple cjoice polls
// to be able to at least display _something_
const total_base = this.poll.multiple ? this.poll.voters_count : this.poll.votes_count
return total_base > 0 ? total_base : this.poll.votes_count
},
maxHeight () {
// keep in sync with CSS!
return 330; // in px
},
containerClasses () {
return {
loading: this.loading
loading: this.loading,
collapsable: this.collapsable,
collapsed: this.collapsed
}
},
choiceIndices () {
@ -70,10 +97,11 @@ export default {
},
methods: {
percentageForOption (count) {
return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
const total = this.totalFractionBase
return total === 0 ? 0 : Math.round(count / total * 100)
},
resultTitle (option) {
return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
return `${option.votes_count}/${this.totalFractionBase} ${this.$t('polls.votes')}`
},
fetchPoll () {
this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
@ -102,6 +130,12 @@ export default {
optionId (index) {
return `poll${this.poll.id}-${index}`
},
peekResults (peeking) {
this.peekingResults = peeking
},
collapse(collapsed) {
this.collapsed = collapsed
},
vote () {
if (this.choiceIndices.length === 0) return
this.loading = true

View file

@ -1,88 +1,140 @@
<template>
<div
ref="pollContainer"
class="poll"
:class="containerClass"
:class="containerClasses"
>
<div
v-for="(option, index) in options"
:key="index"
class="poll-option"
<button
v-show="collapsable && !collapsed"
class="show-less-button button-unstyled -link"
@click.stop.prevent="collapse(true)"
>
{{ $t('polls.show_less') }}
</button>
<button
v-show="collapsed"
class="show-more-button button-unstyled -link"
@click.stop.prevent="collapse(false)"
>
{{ $t('polls.show_full') }}
</button>
<div class="poll-content">
<div
v-if="showResults"
:title="resultTitle(option)"
class="option-result"
v-for="(option, index) in options"
:key="index"
class="poll-option"
>
<div class="option-result-label">
<span class="result-percentage">
{{ percentageForOption(option.votes_count) }}%
</span>
<RichContent
:html="option.title_html"
:handle-links="false"
:emoji="emoji"
<div
v-if="showResults"
:title="resultTitle(option)"
class="option-result"
>
<div class="option-result-label">
<span class="result-percentage">
{{ percentageForOption(option.votes_count) }}%
</span>
<RichContent
:html="option.title_html"
:handle-links="false"
:emoji="emoji"
/>
</div>
<div
class="result-fill"
:style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
/>
</div>
<div
class="result-fill"
:style="{ 'width': `${percentageForOption(option.votes_count)}%` }"
/>
</div>
<div
v-else
@click="activateOption(index)"
>
<input
v-if="poll.multiple"
type="checkbox"
:disabled="loading"
:value="index"
>
<input
v-else
type="radio"
:disabled="loading"
:value="index"
@click="activateOption(index)"
>
<label class="option-vote">
<RichContent
:html="option.title_html"
:handle-links="false"
:emoji="emoji"
/>
</label>
<input
v-if="poll.multiple"
type="checkbox"
:disabled="loading"
:value="index"
>
<input
v-else
type="radio"
:disabled="loading"
:value="index"
>
<label class="option-vote">
<RichContent
:html="option.title_html"
:handle-links="false"
:emoji="emoji"
/>
</label>
</div>
</div>
</div>
<div class="footer faint">
<button
v-if="!showResults"
class="btn button-default poll-vote-button"
type="button"
:disabled="isDisabled"
@click="vote"
>
{{ $t('polls.vote') }}
</button>
<div class="total">
<template v-if="typeof poll.voters_count === 'number'">
{{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }}&nbsp;·&nbsp;
</template>
<template v-else>
{{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}&nbsp;·&nbsp;
</template>
</div>
<span>
<i18n-t
scope="global"
:keypath="expired ? 'polls.expired' : 'polls.expires_in'"
<div class="poll-hint">
<div
v-if="poll.akkoma?.anonymous === true"
class="alert success"
>
<Timeago
:time="expiresAt"
:auto-update="60"
:now-threshold="0"
/>
</i18n-t>
</span>
<FAIcon icon="check-circle" />
&nbsp;
{{ $t('polls.indicate_anonymous') }}
</div>
<div
v-else-if="poll.akkoma?.anonymous === false"
class="alert warning"
>
<FAIcon icon="triangle-exclamation" />
&nbsp;
{{ $t('polls.indicate_disclosure') }}
</div>
</div>
<div class="footer faint">
<button
v-if="!showResults"
class="btn button-default poll-vote-button"
type="button"
:disabled="isDisabled"
@click="vote"
>
{{ $t('polls.vote') }}
</button>
<button
v-if="!showResults"
class="btn button-default poll-results-toggle-button"
type="button"
:disabled="!isDisabled"
@click="peekResults(true)"
>
{{ $t('polls.show_results') }}
</button>
<button
v-else-if="canVote"
class="btn button-default poll-results-toggle-button"
type="button"
@click="peekResults(false)"
>
{{ $t('polls.hide_results') }}
</button>
<div class="total">
<template v-if="typeof poll.voters_count === 'number'">
{{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }}&nbsp;·&nbsp;
</template>
<template v-else>
{{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}&nbsp;·&nbsp;
</template>
</div>
<span>
<i18n-t
scope="global"
:keypath="expired ? 'polls.expired' : 'polls.expires_in'"
>
<Timeago
:time="expiresAt"
:auto-update="60"
:now-threshold="0"
/>
</i18n-t>
</span>
</div>
</div>
</div>
</template>
@ -93,13 +145,58 @@
@import '../../_variables.scss';
.poll {
// keep in sync with js!
max-height: 330px;
overflow-y: hide;
display: flex;
flex-direction: column;
position: relative;
z-index: 1;
&.collapsable:not(.collapsed) {
max-height: unset;
}
&.collapsed {
.poll-content {
mask: linear-gradient(
to bottom,
white 260px,
transparent 330px
);
}
}
.poll-content {
padding-top: 0.375em;
}
.show-less-button, .show-more-button {
z-index: 2;
display: inline-block;
width: 100%;
text-align: center;
}
.show-less-button {
position: relative;
}
.show-more-button {
position: absolute;
margin-top: 260px;
height: 70px;
line-height: 110px;
}
.votes {
display: flex;
flex-direction: column;
margin: 0 0 0.5em;
}
.poll-option {
margin: 0.75em 0.5em;
margin: 0.375em 0.5em;
}
.option-result {
height: 100%;
@ -144,10 +241,13 @@
display: flex;
align-items: center;
}
.poll-hint {
margin: 0.25em 0;
}
&.loading * {
cursor: progress;
}
.poll-vote-button {
.poll-vote-button, .poll-results-toggle-button {
padding: 0 0.5em;
margin-right: 0.5em;
}

View file

@ -24,6 +24,7 @@
<button
v-if="options.length > 2"
class="delete-option button-unstyled -hover-highlight"
type="button"
@click="deleteOption(index)"
>
<FAIcon icon="times" />
@ -32,6 +33,7 @@
<button
v-if="options.length < maxOptions"
class="add-option faint button-unstyled -hover-highlight"
type="button"
@click="addOption"
>
<FAIcon

View file

@ -9,11 +9,13 @@ 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 scopeUtils from 'src/lib/scope_utils.js'
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 +64,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',
@ -77,6 +86,7 @@ const PostStatusForm = {
'quoteId',
'repliedUser',
'attentions',
'copyMessageLanguage',
'copyMessageScope',
'subject',
'disableSubject',
@ -129,16 +139,23 @@ const PostStatusForm = {
this.$refs.textarea.focus()
}
},
setup() {
const {postLanguageOptions} = usePostLanguageOptions()
return {
postLanguageOptions,
}
},
data () {
const preset = this.$route.query.message
let statusText = preset || ''
if (this.replyTo || this.quoteId) {
if (this.replyTo || this.quoteId || this.repliedUser) {
const currentUser = this.$store.state.users.currentUser
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
}
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, interfaceLanguage } = this.$store.getters.mergedConfig
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, alwaysShowSubjectInput } = this.$store.getters.mergedConfig
let statusParams = {
spoilerText: this.subject || '',
@ -149,7 +166,7 @@ const PostStatusForm = {
poll: {},
mediaDescriptions: {},
visibility: this.suggestedVisibility(),
language: interfaceLanguage,
language: this.suggestedLanguage(),
contentType
}
@ -164,7 +181,7 @@ const PostStatusForm = {
poll: this.statusPoll || {},
mediaDescriptions: this.statusMediaDescriptions || {},
visibility: this.statusScope || this.suggestedVisibility(),
language: this.statusLanguage || interfaceLanguage,
language: this.statusLanguage || this.suggestedLanguage(),
contentType: statusContentType
}
}
@ -199,6 +216,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 +234,10 @@ const PostStatusForm = {
preview: null,
previewLoading: false,
emojiInputShown: false,
idempotencyKey: ''
idempotencyKey: '',
activeEmojiInput: undefined,
activeTextInput: undefined,
subjectVisible: showSubject
}
},
computed: {
@ -302,13 +326,11 @@ const PostStatusForm = {
...mapState({
mobileLayout: state => state.interface.mobileLayout
}),
isoLanguages () {
return iso6391.getAllCodes();
}
},
watch: {
'newStatus': {
deep: true,
flush: 'sync',
handler () {
this.statusChanged()
}
@ -321,17 +343,22 @@ const PostStatusForm = {
this.saveDraft()
},
clearStatus () {
const newStatus = this.newStatus
const config = this.$store.getters.mergedConfig
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType,
language: newStatus.language,
nsfw: !!config.sensitiveByDefault,
visibility: this.suggestedVisibility(),
contentType: config.postContentType,
language: this.suggestedLanguage(),
poll: {},
mediaDescriptions: {}
}
const scopeselector = this.$refs.scopeselector
if (scopeselector) {
scopeselector.currentScope = this.newStatus.visibility
}
this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.clearPollForm()
@ -491,7 +518,7 @@ const PostStatusForm = {
addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo)
if (this.$store.getters.mergedConfig.sensitiveIfSubject && this.newStatus.spoilerText !== '') {
if (this.$store.getters.mergedConfig.sensitiveIfSubject && this.newStatus.spoilerText !== '' || !!this.$store.getters.mergedConfig.sensitiveByDefault) {
this.newStatus.nsfw = true
}
this.$emit('resize', { delayed: true })
@ -528,7 +555,7 @@ const PostStatusForm = {
this.uploadingFiles = false
},
type (fileInfo) {
return fileTypeService.fileType(fileInfo.mimetype)
return fileTypeService.fileType(fileInfo)
},
paste (e) {
this.autoPreview()
@ -674,8 +701,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
@ -715,16 +767,19 @@ const PostStatusForm = {
openProfileTab () {
this.$store.dispatch('openSettingsModalTab', 'profile')
},
suggestedVisibility () {
if (this.copyMessageScope) {
if (this.copyMessageScope === 'direct') {
return this.copyMessageScope
}
if (this.copyMessageScope !== 'public' && this.$store.state.users.currentUser.default_scope !== 'private') {
return this.copyMessageScope
}
suggestedLanguage () {
// Make sure the inherited language is actually valid
if (this.postLanguageOptions.find(o => o.value === this.copyMessageLanguage)) {
return this.copyMessageLanguage
}
return this.$store.state.users.currentUser.default_scope
const { postLanguage: defaultPostLanguage, interfaceLanguage } = this.$store.getters.mergedConfig
const postLanguage = defaultPostLanguage || interfaceToISOLanguage(interfaceLanguage)
return postLanguage
},
suggestedVisibility () {
const maxScope = this.copyMessageScope
const defaultScope = this.$store.state.users.currentUser.default_scope
return scopeUtils.negotiate(defaultScope, maxScope)
}
}
}

View file

@ -18,6 +18,7 @@
>
<button
class="button-unstyled -link"
type="button"
@click="openProfileTab"
>
{{ $t('post_status.account_not_locked_warning_link') }}
@ -118,13 +119,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 +136,8 @@
size="1"
class="form-post-subject"
@input="onSubjectInput"
@focus="focusSubjectInput()"
@keydown.exact.enter.prevent
>
</EmojiInput>
<i18n-t
@ -166,13 +172,14 @@
cols="1"
:disabled="posting && !optimisticPosting"
class="form-post-body"
:class="{ 'scrollable-form': !!maxHeight }"
:class="{ 'scrollable-form': !!maxHeight, '-has-subject': subjectVisible }"
@keydown.exact.enter="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,9 +192,11 @@
<div
v-if="!disableScopeSelector"
class="visibility-tray"
:class="{ 'visibility-tray-edit': isEdit }"
>
<scope-selector
v-if="!disableVisibilitySelector"
ref="scopeselector"
:user-default="userDefaultScope"
:original-scope="copyMessageScope"
:initial-scope="newStatus.visibility"
@ -195,47 +204,51 @@
/>
<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"
>
{{ language }}
</option>
</Select>
</div>
<div
v-if="postFormats.length > 1"
class="text-format"
class="format-selector-container"
>
<Select
id="post-content-type"
v-model="newStatus.contentType"
class="form-control"
<div
class="format-selector"
>
<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>
@ -263,6 +276,7 @@
<button
class="emoji-icon button-unstyled"
:title="$t('emoji.add_emoji')"
type="button"
@click="showEmojiPicker"
>
<FAIcon icon="smile-beam" />
@ -272,10 +286,21 @@
class="poll-icon button-unstyled"
:class="{ selected: pollFormVisible }"
:title="$t('polls.add_poll')"
type="button"
@click="togglePollForm"
>
<FAIcon icon="poll-h" />
</button>
<button
v-if="!disableSubject"
class="spoiler-icon button-unstyled"
:class="{ selected: subjectVisible }"
:title="$t('post_status.toggle_content_warning')"
type="button"
@click="toggleSubjectVisible"
>
<FAIcon icon="eye-slash" />
</button>
</div>
<button
v-if="posting"
@ -446,6 +471,10 @@
align-items: baseline;
}
.visibility-tray-edit {
justify-content: right;
}
.visibility-notice.edit-warning {
> :first-child {
margin-top: 0;
@ -456,7 +485,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 +534,11 @@
.poll-icon {
order: 3;
justify-content: center;
}
.spoiler-icon {
order: 4;
justify-content: right;
}
@ -551,6 +591,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 +608,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;
}

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