Compare commits

...

45 Commits

Author SHA1 Message Date
lamp efb46fc29d oneric fix sort 2024-05-15 17:55:16 -07:00
lamp 08ea0383fd fix 403 2024-05-06 02:48:41 -07:00
lamp 4c58e2f4ea fix disabled favorites 2024-05-06 02:17:40 -07:00
lamp 4d139b34d4 merge "Display public favorites on user profiles if enabled"
merged from 6f452d672fe740035cf1d29d03bcda0d39438753 and 1ceffb4e713b4b20d70121fba92d2b50f2d3cadf
2024-05-06 02:00:16 -07:00
lamp 3984d98821 version tab url 2024-05-06 00:31:48 -07:00
fox 87aee5e931 really obnoxious change
revert Prevent replying with a more open visibility than the parent post
2024-05-04 15:45:13 -07:00
floatingghost ed0b403c33 Merge pull request 'Auto-approve followbacks (frontend part)' (#365) from Oneric/akkoma-fe:followbacks-fe into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/365
2024-02-20 16:24:37 +00:00
floatingghost 0f842b300b Merge pull request 'Display profile background of other users' (#371) from Oneric/akkoma-fe:profile-backgrounds into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/371
2024-02-20 16:20:14 +00:00
floatingghost 865cb6f96a Merge pull request 'Add Indonesian translation' (#366) from leap123/akkoma-fe:leap123-patch-1 into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/366
2024-02-19 14:04:34 +00:00
Oneric 050c7df2e6 Display profile background of other users
And add a new frontend setting to hide other people's background.
2024-02-14 18:44:57 +01:00
Oneric a77a9e04d9 Expose new server-side permit_followback setting
Added to backend in https://akkoma.dev/AkkomaGang/akkoma/pulls/674
2024-02-04 22:19:14 +01:00
leap123 a57334991e Add Indonesian translation
The Indonesian translation is technically almost complete, just not added to messages.js
2024-01-19 04:27:26 +00:00
floatingghost 8dce31d0ad Merge pull request 'Improve UX of subject / Content Warning field' (#362) from hazelnoot/akkoma-fe:develop into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/362
2023-12-20 18:49:40 +00:00
Hazel Koehler ea9ad4d600 fix "always show content warning" setting 2023-12-20 12:39:31 -05:00
Hazel Koehler 34e2800f59 add button to toggle the spoiler / CW field 2023-12-16 14:44:26 -05:00
Hazel Koehler 3d65eccf04 use main emoji button for spoiler / CW field 2023-12-16 13:37:59 -05:00
floatingghost d304be654f Merge pull request 'Update build setup instructions' (#343) from norm/pleroma-fe:update-build-setup into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/343
2023-12-15 12:24:33 +00:00
floatingghost aee97fa948 Merge pull request 'Re-added extension checking for still-image' (#346) from Mergan/pleroma-fe:still-image-ultimate into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/346
2023-12-15 12:24:07 +00:00
floatingghost 7da1687f31 Merge pull request 'Use relative font size and set appropriate overflow behavior' (#355) from xarvos/pleroma-fe:update-css into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/355
2023-12-15 12:12:28 +00:00
floatingghost a8f193d4bd Merge pull request 'Stop constant movement of notifications due to changing timestamps' (#353) from Oneric/akkoma-fe:notification-writhing into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/353
2023-12-15 11:57:47 +00:00
floatingghost 81c82e11bc Merge pull request 'Explicitly set SameSite attribute for cookies' (#352) from Oneric/akkoma-fe:cookie-samesite into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/352
2023-12-15 11:54:15 +00:00
floatingghost 00cadce5b4 Merge pull request 'Format dates, times with window.navigator.language instead of UI i18n locale' (#354) from smitten/akkoma-fe:date-locale-fix-cherrypick into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/354
2023-12-15 11:52:59 +00:00
floatingghost 40a08f279b Merge pull request 'Drop broken "@ symbol as icon" setting' (#359) from Oneric/akkoma-fe:at-icon into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/359
2023-11-16 10:41:17 +00:00
Oneric c524a47e6f Drop broken "@ symbol as icon" setting
It was merged into pleroma-fe on 2022-02-03 in
76547fe66d and imported
into akkoma-fe on 2022-06-08 with the merge commit
f6cf509a04.

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

Given it existed in this broken state for nearly one and a half years,
never worked on akkoma-fe and no bugs were filed about this, it appears
nobody cares, so let’s just remove it.
2023-11-15 23:36:19 +01:00
Ngô Ngọc Đức Huy 235c734d37 Use overflow: auto for description
Previously it sets overflow: scroll, so there's an unnecessary
horizontal scroll.
overflow: auto only shows scrollbar when it overflows
2023-11-05 09:32:19 +07:00
Ngô Ngọc Đức Huy deaef1d0b9 Use relative unit for font size 2023-11-05 09:21:01 +07:00
smitten 1b28ec3b72 Match UI i18n configuration to browser locales 2023-11-01 23:10:57 -04:00
smitten c9dc8f00f9 Use window.navigator.language before interface i18n language 2023-10-30 23:56:53 -04:00
Oneric beee99e733 Stop notifications boxes from change size over time
Notifications about favourites and follows use .notification-right,
notifications about replies instead use .heading-right.

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

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

Avoid this, by reserving enough space for any timestamp.

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

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

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

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

Thus instead of fixing the omission for follow-related notifs,
drop direction text from all notification timestamps.
2023-10-24 23:28:45 +02:00
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
Norm 1de62fffcd Update config.example.json link and example domain 2023-10-06 04:52:04 -04:00
Norm 306cea04a1 Use corepack in build instructions 2023-10-06 04:51:59 -04:00
Mergan d9e1bc4d99 Re-added extension checking for still-image
- Bonus refactoring
2023-10-02 15:29:54 -07:00
FloatingGhost 52b0b6f008 add VI to messages.js 2023-10-02 13:28:23 +01:00
floatingghost 8afbe5e3bc Merge pull request 'Making still-image better' (#341) from Mergan/pleroma-fe:still-image-ultimate into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/341
2023-09-25 13:29:29 +00:00
floatingghost 58be48d164 Merge pull request 'Do not copy all emojis in recentEmoji getter' (#340) from sn0w/akkoma-fe:feature/optimize-recent-emojis into develop
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/340
2023-09-25 13:24:12 +00:00
Mergan 1056b89fd1 Disabled aggressive matching for reduced motion (we search for gif now) 2023-09-12 04:32:01 -07:00
Mergan 3e64d78d05 An oopsie 2023-09-12 04:17:28 -07:00
Mergan 3947aafeba Aligning canvas to image 2023-09-12 04:08:47 -07:00
Mergan 345934c2f3 Make label visible on avatar 2023-09-12 03:37:05 -07:00
Mergan 42a13b0f1b Modify label 2023-09-12 03:35:58 -07:00
Mergan e13c4b6b85 Revamped still-image 2023-09-12 02:48:53 -07:00
sn0w 6a1409e09b Do not copy all emojis in recentEmoji getter 2023-09-03 16:19:06 +02:00
FloatingGhost 174f98b1cb don't die on my arm box please 2023-08-05 14:17:42 +01:00
44 changed files with 365 additions and 117 deletions
+1
View File
@@ -1,3 +1,4 @@
platform: linux/amd64
pipeline: pipeline:
lint: lint:
when: when:
+2 -2
View File
@@ -22,7 +22,7 @@ To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/ad
``` bash ``` bash
# install dependencies # install dependencies
npm install -g yarn corepack enable
yarn yarn
# serve with hot reload at localhost:8080 # serve with hot reload at localhost:8080
@@ -37,7 +37,7 @@ npm run unit
# For Contributors: # For Contributors:
You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/config/local.example.json)) to enable some convenience dev options: You can create file `/config/local.json` (see [example](https://akkoma.dev/AkkomaGang/akkoma-fe/src/branch/develop/config/local.example.json)) to enable some convenience dev options:
* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases. * `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. * `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"target": "https://pleroma.soykaf.com/", "target": "https://otp.akkoma.dev/",
"staticConfigPreference": false "staticConfigPreference": false
} }
+3
View File
@@ -70,6 +70,9 @@ Default post formatting option (markdown/bbcode/plaintext/etc...)
### `redirectRootNoLogin`, `redirectRootLogin` ### `redirectRootNoLogin`, `redirectRootLogin`
These two settings should point to where FE should redirect visitor when they login/open up website root These two settings should point to where FE should redirect visitor when they login/open up website root
### `scopeCopy`
Copy post scope (visibility) when replying to a post. Instance-default.
### `sidebarRight` ### `sidebarRight`
Change alignment of sidebar and panels to the right. Defaults to `false`. Change alignment of sidebar and panels to the right. Defaults to `false`.
+6 -1
View File
@@ -64,6 +64,11 @@ export default {
'-' + this.layoutType '-' + this.layoutType
] ]
}, },
pageBackground () {
return this.mergedConfig.displayPageBackgrounds
? this.$store.state.users.displayBackground
: null
},
currentUser () { return this.$store.state.users.currentUser }, currentUser () { return this.$store.state.users.currentUser },
userBackground () { return this.currentUser.background_image }, userBackground () { return this.currentUser.background_image },
instanceBackground () { instanceBackground () {
@@ -71,7 +76,7 @@ export default {
? null ? null
: this.$store.state.instance.background : this.$store.state.instance.background
}, },
background () { return this.userBackground || this.instanceBackground }, background () { return this.pageBackground || this.userBackground || this.instanceBackground },
bgStyle () { bgStyle () {
if (this.background) { if (this.background) {
return { return {
+1 -1
View File
@@ -8,7 +8,7 @@
} }
html { html {
font-size: 14px; font-size: 0.875rem;
// overflow-x: clip causes my browser's tab to crash with SIGILL lul // overflow-x: clip causes my browser's tab to crash with SIGILL lul
} }
+2
View File
@@ -173,8 +173,10 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('redirectRootNoLogin') copyInstanceOption('redirectRootNoLogin')
copyInstanceOption('redirectRootLogin') copyInstanceOption('redirectRootLogin')
copyInstanceOption('showInstanceSpecificPanel') copyInstanceOption('showInstanceSpecificPanel')
copyInstanceOption('minimalScopesMode')
copyInstanceOption('hideMutedPosts') copyInstanceOption('hideMutedPosts')
copyInstanceOption('collapseMessageWithSubject') copyInstanceOption('collapseMessageWithSubject')
copyInstanceOption('scopeCopy')
copyInstanceOption('subjectLineBehavior') copyInstanceOption('subjectLineBehavior')
copyInstanceOption('postContentType') copyInstanceOption('postContentType')
copyInstanceOption('alwaysShowSubjectInput') copyInstanceOption('alwaysShowSubjectInput')
+1 -1
View File
@@ -37,7 +37,7 @@
white-space: pre-line; white-space: pre-line;
word-break: break-word; word-break: break-word;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: scroll; overflow: auto;
} }
&.-static { &.-static {
@@ -4,6 +4,7 @@ const FeaturesPanel = {
computed: { computed: {
whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled }, whoToFollow: function () { return this.$store.state.instance.suggestionsEnabled },
mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable }, mediaProxy: function () { return this.$store.state.instance.mediaProxyAvailable },
minimalScopesMode: function () { return this.$store.state.instance.minimalScopesMode },
textlimit: function () { return this.$store.state.instance.textlimit }, textlimit: function () { return this.$store.state.instance.textlimit },
uploadlimit: function () { return fileSizeFormatService.fileSizeFormat(this.$store.state.instance.uploadlimit) } uploadlimit: function () { return fileSizeFormatService.fileSizeFormat(this.$store.state.instance.uploadlimit) }
} }
@@ -93,9 +93,6 @@ const MentionLink = {
this.highlightType this.highlightType
] ]
}, },
useAtIcon () {
return this.mergedConfig.useAtIcon
},
isRemote () { isRemote () {
return this.userName !== this.userNameFull return this.userName !== this.userNameFull
}, },
@@ -151,7 +151,6 @@
> >
<Timeago <Timeago
:time="notification.created_at" :time="notification.created_at"
:with-direction="true"
:auto-update="240" :auto-update="240"
/> />
</router-link> </router-link>
@@ -105,9 +105,12 @@
flex: 1; flex: 1;
padding-left: 0.8em; padding-left: 0.8em;
min-width: 0; min-width: 0;
}
.heading-right, .notification-right {
.timeago { .timeago {
min-width: 3em; display: inline-block;
min-width: 6em;
text-align: right; text-align: right;
} }
} }
@@ -138,7 +138,7 @@ const PostStatusForm = {
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, 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, interfaceLanguage, alwaysShowSubjectInput } = this.$store.getters.mergedConfig
let statusParams = { let statusParams = {
spoilerText: this.subject || '', spoilerText: this.subject || '',
@@ -199,6 +199,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 { return {
dropFiles: [], dropFiles: [],
uploadingFiles: false, uploadingFiles: false,
@@ -213,7 +217,10 @@ const PostStatusForm = {
preview: null, preview: null,
previewLoading: false, previewLoading: false,
emojiInputShown: false, emojiInputShown: false,
idempotencyKey: '' idempotencyKey: '',
activeEmojiInput: undefined,
activeTextInput: undefined,
subjectVisible: showSubject
} }
}, },
computed: { computed: {
@@ -223,6 +230,9 @@ const PostStatusForm = {
userDefaultScope () { userDefaultScope () {
return this.$store.state.users.currentUser.default_scope return this.$store.state.users.currentUser.default_scope
}, },
showAllScopes () {
return !this.mergedConfig.minimalScopesMode
},
emojiUserSuggestor () { emojiUserSuggestor () {
return suggestor({ return suggestor({
emoji: [ emoji: [
@@ -264,6 +274,9 @@ const PostStatusForm = {
isOverLengthLimit () { isOverLengthLimit () {
return this.hasStatusLengthLimit && (this.charactersLeft < 0) return this.hasStatusLengthLimit && (this.charactersLeft < 0)
}, },
minimalScopesMode () {
return this.$store.state.instance.minimalScopesMode
},
alwaysShowSubject () { alwaysShowSubject () {
return this.mergedConfig.alwaysShowSubjectInput return this.mergedConfig.alwaysShowSubjectInput
}, },
@@ -674,8 +687,33 @@ const PostStatusForm = {
this.$refs['emoji-input'].resize() this.$refs['emoji-input'].resize()
}, },
showEmojiPicker () { showEmojiPicker () {
this.$refs['textarea'].focus() if (!this.activeEmojiInput || !this.activeTextInput)
this.$refs['emoji-input'].triggerShowPicker() 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 () { clearError () {
this.error = null this.error = null
@@ -720,8 +758,10 @@ const PostStatusForm = {
if (this.copyMessageScope === 'direct') { if (this.copyMessageScope === 'direct') {
return this.copyMessageScope return this.copyMessageScope
} }
if (this.copyMessageScope !== 'public' && this.$store.state.users.currentUser.default_scope !== 'private') { if (this.$store.getters.mergedConfig.scopeCopy) {
return this.copyMessageScope if (this.copyMessageScope !== 'public' && this.$store.state.users.currentUser.default_scope !== 'private') {
return this.copyMessageScope
}
} }
} }
return this.$store.state.users.currentUser.default_scope return this.$store.state.users.currentUser.default_scope
@@ -118,13 +118,16 @@
/> />
</div> </div>
<EmojiInput <EmojiInput
v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)" ref="subject-emoji-input"
v-if="subjectVisible"
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
enable-emoji-picker enable-emoji-picker
hide-emoji-button
:suggest="emojiSuggestor" :suggest="emojiSuggestor"
class="form-control" class="form-control"
> >
<input <input
ref="subject-input"
v-model="newStatus.spoilerText" v-model="newStatus.spoilerText"
type="text" type="text"
:placeholder="$t('post_status.content_warning')" :placeholder="$t('post_status.content_warning')"
@@ -132,6 +135,7 @@
size="1" size="1"
class="form-post-subject" class="form-post-subject"
@input="onSubjectInput" @input="onSubjectInput"
@focus="focusSubjectInput()"
> >
</EmojiInput> </EmojiInput>
<i18n-t <i18n-t
@@ -173,6 +177,7 @@
@input="resize" @input="resize"
@compositionupdate="resize" @compositionupdate="resize"
@paste="paste" @paste="paste"
@focus="focusStatusInput()"
/> />
<p <p
v-if="hasStatusLengthLimit" v-if="hasStatusLengthLimit"
@@ -188,6 +193,7 @@
> >
<scope-selector <scope-selector
v-if="!disableVisibilitySelector" v-if="!disableVisibilitySelector"
:show-all="showAllScopes"
:user-default="userDefaultScope" :user-default="userDefaultScope"
:original-scope="copyMessageScope" :original-scope="copyMessageScope"
:initial-scope="newStatus.visibility" :initial-scope="newStatus.visibility"
@@ -276,6 +282,15 @@
> >
<FAIcon icon="poll-h" /> <FAIcon icon="poll-h" />
</button> </button>
<button
v-if="!disableSubject"
class="spoiler-icon button-unstyled"
:class="{ selected: subjectVisible }"
:title="$t('post_status.toggle_content_warning')"
@click="toggleSubjectVisible"
>
<FAIcon icon="eye-slash" />
</button>
</div> </div>
<button <button
v-if="posting" v-if="posting"
@@ -456,7 +471,7 @@
} }
} }
.media-upload-icon, .poll-icon, .emoji-icon { .media-upload-icon, .poll-icon, .emoji-icon, .spoiler-icon {
font-size: 1.85em; font-size: 1.85em;
line-height: 1.1; line-height: 1.1;
flex: 1; flex: 1;
@@ -499,6 +514,11 @@
.poll-icon { .poll-icon {
order: 3; order: 3;
justify-content: center;
}
.spoiler-icon {
order: 4;
justify-content: right; justify-content: right;
} }
@@ -13,14 +13,6 @@ library.add(
faLockOpen faLockOpen
) )
const SCOPE_LEVELS = {
'direct': 0,
'private': 1,
'local': 2,
'unlisted': 2,
'public': 3
}
const ScopeSelector = { const ScopeSelector = {
props: [ props: [
'showAll', 'showAll',
@@ -65,15 +57,11 @@ const ScopeSelector = {
}, },
methods: { methods: {
shouldShow (scope) { shouldShow (scope) {
if (!this.originalScope) { return this.showAll ||
return true this.currentScope === scope ||
} this.originalScope === scope ||
this.userDefault === scope ||
if (this.originalScope === 'local') { scope === 'direct'
return scope === 'direct' || scope === 'local'
}
return SCOPE_LEVELS[scope] <= SCOPE_LEVELS[this.originalScope]
}, },
changeVis (scope) { changeVis (scope) {
this.currentScope = scope this.currentScope = scope
@@ -146,6 +146,11 @@
{{ $t('settings.show_wider_shortcuts') }} {{ $t('settings.show_wider_shortcuts') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<BooleanSetting path="displayPageBackgrounds">
{{ $t('settings.show_page_backgrounds') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting path="stopGifs"> <BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }} {{ $t('settings.stop_gifs') }}
@@ -483,14 +488,6 @@
</BooleanSetting> </BooleanSetting>
</li> </li>
</ul> </ul>
<li>
<BooleanSetting
path="useAtIcon"
expert="1"
>
{{ $t('settings.use_at_icon') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting path="mentionLinkShowAvatar"> <BooleanSetting path="mentionLinkShowAvatar">
{{ $t('settings.mention_link_show_avatar') }} {{ $t('settings.mention_link_show_avatar') }}
@@ -545,12 +542,18 @@
{{ $t('settings.default_vis') }} <ServerSideIndicator :server-side="true" /> {{ $t('settings.default_vis') }} <ServerSideIndicator :server-side="true" />
<ScopeSelector <ScopeSelector
class="scope-selector" class="scope-selector"
:show-all="true"
:user-default="serverSide_defaultScope" :user-default="serverSide_defaultScope"
:initial-scope="serverSide_defaultScope" :initial-scope="serverSide_defaultScope"
:on-scope-change="changeDefaultScope" :on-scope-change="changeDefaultScope"
/> />
</label> </label>
</li> </li>
<li>
<BooleanSetting path="minimalScopesMode">
{{ $t('settings.minimal_scopes_mode') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting path="sensitiveByDefault"> <BooleanSetting path="sensitiveByDefault">
{{ $t('settings.sensitive_by_default') }} {{ $t('settings.sensitive_by_default') }}
@@ -561,6 +564,14 @@
{{ $t('settings.sensitive_if_subject') }} {{ $t('settings.sensitive_if_subject') }}
</BooleanSetting> </BooleanSetting>
</li> </li>
<li>
<BooleanSetting
path="scopeCopy"
expert="1"
>
{{ $t('settings.scope_copy') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting <BooleanSetting
path="alwaysShowSubjectInput" path="alwaysShowSubjectInput"
@@ -588,6 +599,14 @@
{{ $t('settings.post_status_content_type') }} {{ $t('settings.post_status_content_type') }}
</ChoiceSetting> </ChoiceSetting>
</li> </li>
<li>
<BooleanSetting
path="minimalScopesMode"
expert="1"
>
{{ $t('settings.minimal_scopes_mode') }}
</BooleanSetting>
</li>
<li> <li>
<BooleanSetting <BooleanSetting
path="alwaysShowNewPostButton" path="alwaysShowNewPostButton"
@@ -33,6 +33,7 @@ const ProfileTab = {
newName: this.$store.state.users.currentUser.name_unescaped, newName: this.$store.state.users.currentUser.name_unescaped,
newBio: unescape(this.$store.state.users.currentUser.description), newBio: unescape(this.$store.state.users.currentUser.description),
newLocked: this.$store.state.users.currentUser.locked, newLocked: this.$store.state.users.currentUser.locked,
newPermitFollowback: this.$store.state.users.currentUser.permit_followback,
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })), newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
showRole: this.$store.state.users.currentUser.show_role, showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role, role: this.$store.state.users.currentUser.role,
@@ -135,6 +136,7 @@ const ProfileTab = {
bot: this.bot, bot: this.bot,
show_role: this.showRole, show_role: this.showRole,
status_ttl_days: this.expirePosts ? this.newPostTTLDays : -1, status_ttl_days: this.expirePosts ? this.newPostTTLDays : -1,
permit_followback: this.permit_followback,
accepts_direct_messages_from: this.userAcceptsDirectMessagesFrom accepts_direct_messages_from: this.userAcceptsDirectMessagesFrom
/* eslint-enable camelcase */ /* eslint-enable camelcase */
} }
@@ -259,6 +259,19 @@
<BooleanSetting path="serverSide_locked"> <BooleanSetting path="serverSide_locked">
{{ $t('settings.lock_account_description') }} {{ $t('settings.lock_account_description') }}
</BooleanSetting> </BooleanSetting>
<ul
class="setting-list suboptions"
:class="[{disabled: !serverSide_locked}]"
>
<li>
<BooleanSetting
path="serverSide_permitFollowback"
:disabled="!serverSide_locked"
>
{{ $t('settings.permit_followback_description') }}
</BooleanSetting>
</li>
</ul>
</li> </li>
<li> <li>
<BooleanSetting path="serverSide_discoverable"> <BooleanSetting path="serverSide_discoverable">
@@ -1,6 +1,6 @@
import { extractCommit } from 'src/services/version/version.service' import { extractCommit } from 'src/services/version/version.service'
const pleromaFeCommitUrl = 'https://akkoma.dev/AkkomaGang/pleroma-fe/commit/' const pleromaFeCommitUrl = 'https://akkoma.dev/lamp/akkoma-fe/commit/'
const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/' const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/'
const VersionTab = { const VersionTab = {
+1 -1
View File
@@ -190,7 +190,7 @@
> >
<Timeago <Timeago
:time="status.created_at" :time="status.created_at"
:with-direction="true" :with-direction="!compact"
:auto-update="60" :auto-update="60"
/> />
</router-link> </router-link>
+118 -26
View File
@@ -13,6 +13,7 @@ const StillImage = {
return { return {
stopGifs: this.$store.getters.mergedConfig.stopGifs || window.matchMedia('(prefers-reduced-motion: reduce)').matches, stopGifs: this.$store.getters.mergedConfig.stopGifs || window.matchMedia('(prefers-reduced-motion: reduce)').matches,
isAnimated: false, isAnimated: false,
imageTypeLabel: ''
} }
}, },
computed: { computed: {
@@ -39,27 +40,24 @@ const StillImage = {
this.imageLoadError && this.imageLoadError() this.imageLoadError && this.imageLoadError()
}, },
detectAnimation (image) { detectAnimation (image) {
// If there are no file extensions, the mimetype isn't set, and no mediaproxy is available, we can't figure out
// the mimetype of the image.
const hasFileExtension = this.src.split('/').pop().includes('.') // TODO: Better check?
const mediaProxyAvailable = this.$store.state.instance.mediaProxyAvailable const mediaProxyAvailable = this.$store.state.instance.mediaProxyAvailable
if (!hasFileExtension && this.mimetype === undefined && !mediaProxyAvailable) {
if (!mediaProxyAvailable) {
// It's a bit aggressive to assume all images we can't find the mimetype of is animated, but necessary for // It's a bit aggressive to assume all images we can't find the mimetype of is animated, but necessary for
// people in need of reduced motion accessibility. As such, we'll consider those images animated if the user // people in need of reduced motion accessibility. As such, we'll consider those images animated if the user
// agent is set to prefer reduced motion. Otherwise, it'll just be used as an early exit. // agent is set to prefer reduced motion. Otherwise, it'll just be used as an early exit.
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
// Since the canvas and images are not pixel-perfect matching (due to scaling),
// It makes the images jiggle on hover, which is not ideal for accessibility, methinks
this.isAnimated = true this.isAnimated = true
return return
}
this.detectWithoutMediaProxy(image)
} else {
this.detectWithMediaProxy(image)
} }
},
if (this.mimetype === 'image/gif' || this.src.endsWith('.gif')) { detectAnimationWithFetch (image) {
this.isAnimated = true
return
}
// harmless CORS errors without-- clean console with
if (!mediaProxyAvailable) return
// Animated JPEGs?
if (!(this.src.endsWith('.webp') || this.src.endsWith('.png'))) return
// Browser Cache should ensure image doesn't get loaded twice if cache exists // Browser Cache should ensure image doesn't get loaded twice if cache exists
fetch(image.src, { fetch(image.src, {
referrerPolicy: 'same-origin' referrerPolicy: 'same-origin'
@@ -68,12 +66,20 @@ const StillImage = {
// We don't need to read the whole file so only call it once // We don't need to read the whole file so only call it once
data.body.getReader().read() data.body.getReader().read()
.then(reader => { .then(reader => {
if (this.src.endsWith('.webp') && this.isAnimatedWEBP(reader.value)) { // Ordered from least to most intensive
if (this.isGIF(reader.value)) {
this.isAnimated = true this.isAnimated = true
this.setLabel('GIF')
return return
} }
if (this.src.endsWith('.png') && this.isAnimatedPNG(reader.value)) { if (this.isAnimatedWEBP(reader.value)) {
this.isAnimated = true this.isAnimated = true
this.setLabel('WEBP')
return
}
if (this.isAnimatedPNG(reader.value)) {
this.isAnimated = true
this.setLabel('APNG')
} }
}) })
}) })
@@ -81,6 +87,53 @@ const StillImage = {
// this.imageLoadError && this.imageLoadError() // this.imageLoadError && this.imageLoadError()
}) })
}, },
detectWithMediaProxy (image) {
this.detectAnimationWithFetch(image)
},
detectWithoutMediaProxy (image) {
// We'll just assume that gifs and webp are animated
const extension = image.src.split('.').pop().toLowerCase()
if (extension === 'gif') {
this.isAnimated = true
this.setLabel('GIF')
return
}
if (extension === 'webp') {
this.isAnimated = true
this.setLabel('WEBP')
return
}
// Beware the apng! use this if ye dare
// if (extension === 'png') {
// this.isAnimated = true
// this.setLabel('PNG')
// return
// }
// Hail mary for extensionless
if (extension.includes('/')) {
// Don't mind the CORS error barrage
this.detectAnimationWithFetch(image)
}
},
setLabel (name) {
this.imageTypeLabel = name;
},
isGIF (data) {
// I am a perfectly sane individual
//
// GIF HEADER CHUNK
// === START HEADER ===
// 47 49 46 38 ("GIF8")
const gifHeader = [0x47, 0x49, 0x46];
for (let i = 0; i < 3; i++) {
if (data[i] !== gifHeader[i]) {
return false;
}
}
return true
},
isAnimatedWEBP (data) { isAnimatedWEBP (data) {
/** /**
* WEBP HEADER CHUNK * WEBP HEADER CHUNK
@@ -114,15 +167,54 @@ const StillImage = {
const idatPos = str.indexOf('IDAT') const idatPos = str.indexOf('IDAT')
return (str.substring(0, idatPos > 0 ? idatPos : 0).indexOf('acTL') > 0) return (str.substring(0, idatPos > 0 ? idatPos : 0).indexOf('acTL') > 0)
}, },
drawThumbnail () { drawThumbnail() {
const canvas = this.$refs.canvas const canvas = this.$refs.canvas;
if (!this.$refs.canvas) return if (!canvas) return;
const image = this.$refs.src
const width = image.naturalWidth const context = canvas.getContext('2d');
const height = image.naturalHeight const image = this.$refs.src;
canvas.width = width const parentElement = canvas.parentElement;
canvas.height = height
canvas.getContext('2d').drawImage(image, 0, 0, width, height) // Draw the quick, unscaled version first
context.drawImage(image, 0, 0, parentElement.clientWidth, parentElement.clientHeight);
// Use requestAnimationFrame to schedule the scaling to the next frame
requestAnimationFrame(() => {
// Compute scaling ratio between the natural dimensions of the image and its display dimensions
const scalingRatioWidth = parentElement.clientWidth / image.naturalWidth;
const scalingRatioHeight = parentElement.clientHeight / image.naturalHeight;
// Adjust for high-DPI displays
const ratio = window.devicePixelRatio || 1;
canvas.width = image.naturalWidth * scalingRatioWidth * ratio;
canvas.height = image.naturalHeight * scalingRatioHeight * ratio;
canvas.style.width = `${parentElement.clientWidth}px`;
canvas.style.height = `${parentElement.clientHeight}px`;
context.scale(ratio, ratio);
// Maintain the aspect ratio of the image
const imgAspectRatio = image.naturalWidth / image.naturalHeight;
const canvasAspectRatio = parentElement.clientWidth / parentElement.clientHeight;
let drawWidth, drawHeight;
if (imgAspectRatio > canvasAspectRatio) {
drawWidth = parentElement.clientWidth;
drawHeight = parentElement.clientWidth / imgAspectRatio;
} else {
drawHeight = parentElement.clientHeight;
drawWidth = parentElement.clientHeight * imgAspectRatio;
}
context.clearRect(0, 0, canvas.width, canvas.height); // Clear the previous unscaled image
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = 'high';
// Draw the good one for realsies
const dx = (parentElement.clientWidth - drawWidth) / 2;
const dy = (parentElement.clientHeight - drawHeight) / 2;
context.drawImage(image, dx, dy, drawWidth, drawHeight);
});
} }
}, },
updated () { updated () {
+21 -19
View File
@@ -1,9 +1,15 @@
<template> <template>
<div <div
ref="still-image"
class="still-image" class="still-image"
:class="{ animated: animated }" :class="{ animated: animated }"
:style="style" :style="style"
> >
<div
v-if="animated && imageTypeLabel"
class="image-type-label">
{{ imageTypeLabel }}
</div>
<canvas <canvas
v-if="animated" v-if="animated"
ref="canvas" ref="canvas"
@@ -57,30 +63,26 @@
} }
} }
&.animated { .image-type-label {
&::before { position: absolute;
zoom: var(--_still_image-label-scale, 1); top: 0.25em;
content: 'gif'; left: 0.25em;
position: absolute; line-height: 1;
line-height: 1; font-size: 0.6em;
font-size: 0.7em; background: rgba(127, 127, 127, 0.5);
top: 0.5em; color: #fff;
left: 0.5em; padding: 2px 4px;
background: rgba(127, 127, 127, 0.5); border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
color: #fff; z-index: 2;
display: block; visibility: var(--_still-image-label-visibility, visible);
padding: 2px 4px; }
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
z-index: 2;
visibility: var(--_still-image-label-visibility, visible);
}
&.animated {
&:hover canvas { &:hover canvas {
display: none; display: none;
} }
&:hover::before { &:hover .image-type-label {
visibility: var(--_still-image-label-visibility, hidden); visibility: var(--_still-image-label-visibility, hidden);
} }
+1 -1
View File
@@ -33,7 +33,7 @@
--_avatarShadowBox: var(--avatarStatusShadow); --_avatarShadowBox: var(--avatarStatusShadow);
--_avatarShadowFilter: var(--avatarStatusShadowFilter); --_avatarShadowFilter: var(--avatarStatusShadowFilter);
--_avatarShadowInset: var(--avatarStatusShadowInset); --_avatarShadowInset: var(--avatarStatusShadowInset);
--_still-image-label-visibility: hidden; // --_still-image-label-visibility: hidden;
display: inline-block; display: inline-block;
position: relative; position: relative;
+12 -4
View File
@@ -97,6 +97,9 @@ const UserProfile = {
followersTabVisible () { followersTabVisible () {
return this.isUs || !this.user.hide_followers return this.isUs || !this.user.hide_followers
}, },
favoritesTabVisible () {
return this.isUs || !this.user.hide_favorites
},
currentUser () { currentUser () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
@@ -114,14 +117,14 @@ const UserProfile = {
replies: 'replies', replies: 'replies',
media: 'media' media: 'media'
} }
// only we can see our own favourites
if (this.isUs) timelineTabMap['favorites'] = 'favorites' if (this.favoritesTabVisible) timelineTabMap['favorites'] = 'favorites'
const timeline = timelineTabMap[nextTab] const timeline = timelineTabMap[nextTab]
if (timeline) { if (timeline) {
this.stopFetching() this.stopFetching()
this.$store.dispatch('startFetchingTimeline', { timeline: timeline, userId: this.userId }) this.$store.dispatch('startFetchingTimeline', { timeline: timeline, userId: nextTab == 'favorites' && this.isUs ? null : this.userId })
} }
}, },
load (userNameOrId) { load (userNameOrId) {
@@ -145,10 +148,12 @@ const UserProfile = {
if (user) { if (user) {
loadById(user.id) loadById(user.id)
this.note = user.relationship.note this.note = user.relationship.note
this.$store.dispatch('setDisplayBackground', user.background_image)
} else { } else {
this.$store.dispatch('fetchUser', userNameOrId) this.$store.dispatch('fetchUser', userNameOrId)
.then(({ id, relationship }) => { .then(({ id, relationship, background_image }) => {
this.note = relationship.note this.note = relationship.note
this.$store.dispatch('setDisplayBackground', background_image)
return loadById(id) return loadById(id)
}) })
.catch((reason) => { .catch((reason) => {
@@ -225,6 +230,9 @@ const UserProfile = {
Conversation, Conversation,
RichContent, RichContent,
FollowedTagList FollowedTagList
},
beforeRouteLeave(to, from) {
this.$store.dispatch('setDisplayBackground', null)
} }
} }
+2 -2
View File
@@ -165,14 +165,14 @@
:footer-slipgate="footerRef" :footer-slipgate="footerRef"
/> />
<Timeline <Timeline
v-if="isUs" v-if="favoritesTabVisible"
key="favorites" key="favorites"
:label="$t('user_card.favorites')" :label="$t('user_card.favorites')"
:disabled="!isUs"
:embedded="true" :embedded="true"
:title="$t('user_card.favorites')" :title="$t('user_card.favorites')"
timeline-name="favorites" timeline-name="favorites"
:timeline="favorites" :timeline="favorites"
:user-id="isUs ? undefined : userId"
:in-profile="true" :in-profile="true"
:footer-slipgate="footerRef" :footer-slipgate="footerRef"
/> />
-1
View File
@@ -884,7 +884,6 @@
"upload_a_photo": "Pujar una foto", "upload_a_photo": "Pujar una foto",
"useStreamingApi": "Rebre apunts i notificacions en temps real", "useStreamingApi": "Rebre apunts i notificacions en temps real",
"useStreamingApiWarning": "És genial emprar-lo. Si es trenca, refresca, suposo?", "useStreamingApiWarning": "És genial emprar-lo. Si es trenca, refresca, suposo?",
"use_at_icon": "Mostra el símbol {'@'} com a icona enlloc de text",
"use_contain_fit": "No retallar els adjunts en miniatures", "use_contain_fit": "No retallar els adjunts en miniatures",
"use_one_click_nsfw": "Obre els adjunts NSFW amb només un clic", "use_one_click_nsfw": "Obre els adjunts NSFW amb només un clic",
"user_mutes": "Usuaris", "user_mutes": "Usuaris",
-1
View File
@@ -916,7 +916,6 @@
"upload_a_photo": "Lade ein Foto hoch", "upload_a_photo": "Lade ein Foto hoch",
"useStreamingApi": "Empfange Posts und Benachrichtigungen in Echtzeit", "useStreamingApi": "Empfange Posts und Benachrichtigungen in Echtzeit",
"useStreamingApiWarning": "(Nicht empfohlen, experimentell, bekannt dafür, Posts zu überspringen)", "useStreamingApiWarning": "(Nicht empfohlen, experimentell, bekannt dafür, Posts zu überspringen)",
"use_at_icon": "{'@'}-Symbol als Icon und nicht als Text anzeigen",
"use_blurhash": "Blurhash für NSFW-Vorschauen verwenden", "use_blurhash": "Blurhash für NSFW-Vorschauen verwenden",
"use_contain_fit": "Vorschaubilder nicht zuschneiden", "use_contain_fit": "Vorschaubilder nicht zuschneiden",
"use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen", "use_one_click_nsfw": "Heikle Anhänge mit nur einem Klick öffnen",
+3 -1
View File
@@ -380,6 +380,7 @@
"text/x.misskeymarkdown": "MFM" "text/x.misskeymarkdown": "MFM"
}, },
"content_warning": "Content Warning (optional)", "content_warning": "Content Warning (optional)",
"toggle_content_warning": "Toggle content warning",
"default": "Just arrived at Luna Nova Academy", "default": "Just arrived at Luna Nova Academy",
"direct_warning_to_all": "This post will be visible to all the mentioned users.", "direct_warning_to_all": "This post will be visible to all the mentioned users.",
"direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.", "direct_warning_to_first_only": "This post will only be visible to the mentioned users at the beginning of the message.",
@@ -600,6 +601,7 @@
"list_aliases_error": "Error fetching aliases: {error}", "list_aliases_error": "Error fetching aliases: {error}",
"list_backups_error": "Error fetching backup list: {error}", "list_backups_error": "Error fetching backup list: {error}",
"lock_account_description": "Restrict your account to approved followers only", "lock_account_description": "Restrict your account to approved followers only",
"permit_followback_description": "Automatically approve requests from already followed users",
"loop_video": "Loop videos", "loop_video": "Loop videos",
"loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")", "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
"mascot": "Mastodon FE Mascot", "mascot": "Mastodon FE Mascot",
@@ -749,6 +751,7 @@
"show_nav_shortcuts": "Show extra navigation shortcuts in top panel", "show_nav_shortcuts": "Show extra navigation shortcuts in top panel",
"show_panel_nav_shortcuts": "Show timeline navigation shortcuts at the top of the panel", "show_panel_nav_shortcuts": "Show timeline navigation shortcuts at the top of the panel",
"show_scrollbars": "Show side column's scrollbars", "show_scrollbars": "Show side column's scrollbars",
"show_page_backgrounds": "Show page-specific backgrounds, e.g. for user profiles",
"show_wider_shortcuts": "Show wider gap between top panel shortcuts", "show_wider_shortcuts": "Show wider gap between top panel shortcuts",
"show_yous": "Show (You)s", "show_yous": "Show (You)s",
"stop_gifs": "Pause animated images until you hover on them", "stop_gifs": "Pause animated images until you hover on them",
@@ -922,7 +925,6 @@
"upload_a_photo": "Upload a photo", "upload_a_photo": "Upload a photo",
"useStreamingApi": "Receive posts and notifications real-time", "useStreamingApi": "Receive posts and notifications real-time",
"useStreamingApiWarning": "It's cool use it. If it breaks refresh I guess?", "useStreamingApiWarning": "It's cool use it. If it breaks refresh I guess?",
"use_at_icon": "Display {'@'} symbol as an icon instead of text",
"use_contain_fit": "Don't crop the attachment in thumbnails", "use_contain_fit": "Don't crop the attachment in thumbnails",
"use_one_click_nsfw": "Open NSFW attachments with just one click", "use_one_click_nsfw": "Open NSFW attachments with just one click",
"user_mutes": "Users", "user_mutes": "Users",
-1
View File
@@ -920,7 +920,6 @@
"upload_a_photo": "Envoyer une photo", "upload_a_photo": "Envoyer une photo",
"useStreamingApi": "Recevoir les messages et notifications en temps réel", "useStreamingApi": "Recevoir les messages et notifications en temps réel",
"useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)", "useStreamingApiWarning": "(Non recommandé, expérimental, connu pour rater des messages)",
"use_at_icon": "Afficher le symbol {'@'} comme une image",
"use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes", "use_contain_fit": "Ne pas rogner les miniatures des pièces-jointes",
"use_one_click_nsfw": "Ouvrir les pièces-jointes sensibles avec un seul clic", "use_one_click_nsfw": "Ouvrir les pièces-jointes sensibles avec un seul clic",
"user_mutes": "Comptes", "user_mutes": "Comptes",
-1
View File
@@ -921,7 +921,6 @@
"upload_a_photo": "画像をアップロード", "upload_a_photo": "画像をアップロード",
"useStreamingApi": "投稿と通知を、すぐに受け取る", "useStreamingApi": "投稿と通知を、すぐに受け取る",
"useStreamingApiWarning": "(実験中で、投稿を取りこぼすかもしれないので、おすすめしません)", "useStreamingApiWarning": "(実験中で、投稿を取りこぼすかもしれないので、おすすめしません)",
"use_at_icon": "{'@'}マークをアイコンにする",
"use_contain_fit": "画像のサムネイルを、切り抜かない", "use_contain_fit": "画像のサムネイルを、切り抜かない",
"use_one_click_nsfw": "NSFWなファイルを1クリックで開く", "use_one_click_nsfw": "NSFWなファイルを1クリックで開く",
"user_mutes": "ユーザー", "user_mutes": "ユーザー",
+2
View File
@@ -21,6 +21,7 @@ const loaders = {
ga: () => import('./ga.json'), ga: () => import('./ga.json'),
he: () => import('./he.json'), he: () => import('./he.json'),
hu: () => import('./hu.json'), hu: () => import('./hu.json'),
id: () => import('./id.json'),
it: () => import('./it.json'), it: () => import('./it.json'),
ja: () => import('./ja_pedantic.json'), ja: () => import('./ja_pedantic.json'),
ja_easy: () => import('./ja_easy.json'), ja_easy: () => import('./ja_easy.json'),
@@ -35,6 +36,7 @@ const loaders = {
sk: () => import('./sk.json'), sk: () => import('./sk.json'),
te: () => import('./te.json'), te: () => import('./te.json'),
uk: () => import('./uk.json'), uk: () => import('./uk.json'),
vi: () => import('./vi.json'),
zh: () => import('./zh.json'), zh: () => import('./zh.json'),
zh_Hant: () => import('./zh_Hant.json') zh_Hant: () => import('./zh_Hant.json')
} }
-1
View File
@@ -918,7 +918,6 @@
"upload_a_photo": "Foto uploaden", "upload_a_photo": "Foto uploaden",
"useStreamingApi": "Berichten en meldingen in real-time ontvangen", "useStreamingApi": "Berichten en meldingen in real-time ontvangen",
"useStreamingApiWarning": "Iets experimenteels met berichten streamen uwu miss kun je beter uit laten ofzo?", "useStreamingApiWarning": "Iets experimenteels met berichten streamen uwu miss kun je beter uit laten ofzo?",
"use_at_icon": "{'@'} symbool als icoon tonen in plaats van tekst",
"use_blurhash": "Waas tonen over NSFW-miniaturen", "use_blurhash": "Waas tonen over NSFW-miniaturen",
"use_contain_fit": "Bijlage in miniaturen niet bijsnijden", "use_contain_fit": "Bijlage in miniaturen niet bijsnijden",
"use_one_click_nsfw": "Gevoelige bijlagen met slechts één klik openen", "use_one_click_nsfw": "Gevoelige bijlagen met slechts één klik openen",
-1
View File
@@ -922,7 +922,6 @@
"upload_a_photo": "Вивантажити фото", "upload_a_photo": "Вивантажити фото",
"useStreamingApi": "Отримувати дописи та сповіщення наживо", "useStreamingApi": "Отримувати дописи та сповіщення наживо",
"useStreamingApiWarning": "Загалом працює. Якщо не зовсім, спробуєте оновити сторінку?", "useStreamingApiWarning": "Загалом працює. Якщо не зовсім, спробуєте оновити сторінку?",
"use_at_icon": "Значок {'@'} замість символу",
"use_blurhash": "Показувати дражливі мініатюри (як розмиті кольори)", "use_blurhash": "Показувати дражливі мініатюри (як розмиті кольори)",
"use_contain_fit": "Не обрізати краї мініатюр", "use_contain_fit": "Не обрізати краї мініатюр",
"use_one_click_nsfw": "Відкривати NSFW вкладення одним кліком миші", "use_one_click_nsfw": "Відкривати NSFW вкладення одним кліком миші",
-1
View File
@@ -922,7 +922,6 @@
"upload_a_photo": "上传照片", "upload_a_photo": "上传照片",
"useStreamingApi": "实时接收帖文和通知", "useStreamingApi": "实时接收帖文和通知",
"useStreamingApiWarning": "十分炫酷推荐使用。要是崩了试试刷新?", "useStreamingApiWarning": "十分炫酷推荐使用。要是崩了试试刷新?",
"use_at_icon": "将 {'@'} 符号显示为图标而不是文本",
"use_blurhash": "对NSFW的缩略图使用模糊处理", "use_blurhash": "对NSFW的缩略图使用模糊处理",
"use_contain_fit": "生成缩略图时不要裁剪附件", "use_contain_fit": "生成缩略图时不要裁剪附件",
"use_one_click_nsfw": "点击一次以打开工作场所不适宜(NSFW)的附件", "use_one_click_nsfw": "点击一次以打开工作场所不适宜(NSFW)的附件",
+4 -2
View File
@@ -55,6 +55,7 @@ export const defaultState = {
alwaysShowNewPostButton: false, alwaysShowNewPostButton: false,
autohideFloatingPostButton: false, autohideFloatingPostButton: false,
pauseOnUnfocused: true, pauseOnUnfocused: true,
displayPageBackgrounds: true,
stopGifs: undefined, stopGifs: undefined,
replyVisibility: 'all', replyVisibility: 'all',
thirdColumnMode: 'notifications', thirdColumnMode: 'notifications',
@@ -76,9 +77,11 @@ export const defaultState = {
hideScopeNotice: false, hideScopeNotice: false,
useStreamingApi: false, useStreamingApi: false,
sidebarRight: undefined, // instance default sidebarRight: undefined, // instance default
scopeCopy: undefined, // instance default
subjectLineBehavior: undefined, // instance default subjectLineBehavior: undefined, // instance default
alwaysShowSubjectInput: undefined, // instance default alwaysShowSubjectInput: undefined, // instance default
postContentType: undefined, // instance default postContentType: undefined, // instance default
minimalScopesMode: undefined, // instance default
// This hides statuses filtered via a word filter // This hides statuses filtered via a word filter
hideFilteredStatuses: undefined, // instance default hideFilteredStatuses: undefined, // instance default
modalOnRepeat: undefined, // instance default modalOnRepeat: undefined, // instance default
@@ -95,7 +98,6 @@ export const defaultState = {
disableStickyHeaders: false, disableStickyHeaders: false,
showScrollbars: false, showScrollbars: false,
greentext: undefined, // instance default greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default mentionLinkDisplay: undefined, // instance default
mentionLinkShowTooltip: undefined, // instance default mentionLinkShowTooltip: undefined, // instance default
mentionLinkShowAvatar: undefined, // instance default mentionLinkShowAvatar: undefined, // instance default
@@ -229,7 +231,7 @@ const config = {
break break
case 'interfaceLanguage': case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value) messages.setLanguage(this.getters.i18n, value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value)) Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value), {sameSite: 'Lax'})
dispatch('setInstanceOption', { name: 'interfaceLanguage', value }) dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
break break
case 'thirdColumnMode': case 'thirdColumnMode':
+2 -1
View File
@@ -21,7 +21,6 @@ const defaultState = {
background: '/static/aurora_borealis.jpg', background: '/static/aurora_borealis.jpg',
collapseMessageWithSubject: true, collapseMessageWithSubject: true,
greentext: false, greentext: false,
useAtIcon: false,
mentionLinkDisplay: 'short', mentionLinkDisplay: 'short',
mentionLinkShowTooltip: true, mentionLinkShowTooltip: true,
mentionLinkShowAvatar: false, mentionLinkShowAvatar: false,
@@ -53,10 +52,12 @@ const defaultState = {
logoMargin: '.2em', logoMargin: '.2em',
logoMask: true, logoMask: true,
logoLeft: false, logoLeft: false,
minimalScopesMode: false,
nsfwCensorImage: undefined, nsfwCensorImage: undefined,
postContentType: 'text/plain', postContentType: 'text/plain',
redirectRootLogin: '/main/friends', redirectRootLogin: '/main/friends',
redirectRootNoLogin: '/main/all', redirectRootNoLogin: '/main/all',
scopeCopy: true,
showFeaturesPanel: true, showFeaturesPanel: true,
showInstanceSpecificPanel: false, showInstanceSpecificPanel: false,
showNavShortcuts: true, showNavShortcuts: true,
+9 -2
View File
@@ -37,11 +37,18 @@ const recentEmojis = {
getters: { getters: {
recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => { recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => {
const allEmojis = rootState.instance.emoji.concat(rootState.instance.customEmoji) let comparator = emoji => emoji.displayText === displayText
let emojiObject = allEmojis.find(emoji => emoji.displayText === displayText)
let emojiObject = rootState.instance.emoji.find(comparator)
if (emojiObject !== undefined) { if (emojiObject !== undefined) {
objects.push(emojiObject) objects.push(emojiObject)
} else {
emojiObject = rootState.instance.customEmoji.find(comparator)
if (emojiObject !== undefined) {
objects.push(emojiObject)
}
} }
return objects return objects
}, []), }, []),
}, },
+4
View File
@@ -47,6 +47,10 @@ export const settingsMap = {
}, },
// Privacy // Privacy
'locked': 'locked', 'locked': 'locked',
'permitFollowback': {
get: 'akkoma.permit_followback',
set: 'permit_followback'
},
'allowFollowingMove': { 'allowFollowingMove': {
get: 'pleroma.allow_following_move', get: 'pleroma.allow_following_move',
set: 'allow_following_move' set: 'allow_following_move'
+1 -1
View File
@@ -314,7 +314,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
}) })
// Keep the visible statuses sorted // Keep the visible statuses sorted
if (timeline && !(timeline === 'bookmarks')) { if (timeline && !(['favorites', 'publicFavorites', 'bookmarks'].includes(timeline))) {
sortTimeline(timelineObject) sortTimeline(timelineObject)
} }
} }
+9
View File
@@ -135,6 +135,10 @@ export const mutations = {
const user = state.usersObject[id] const user = state.usersObject[id]
user['deactivated'] = deactivated user['deactivated'] = deactivated
}, },
setDisplayBackground(state, url) {
console.log("Commiting user profile bg mutation")
state.displayBackground = url
},
setCurrentUser (state, user) { setCurrentUser (state, user) {
state.lastLoginName = user.screen_name state.lastLoginName = user.screen_name
state.currentUser = mergeWith(state.currentUser || {}, user, mergeArrayLength) state.currentUser = mergeWith(state.currentUser || {}, user, mergeArrayLength)
@@ -307,6 +311,7 @@ export const defaultState = {
currentUser: false, currentUser: false,
users: [], users: [],
usersObject: {}, usersObject: {},
displayBackground: null,
signUpPending: false, signUpPending: false,
signUpErrors: [], signUpErrors: [],
relationships: {}, relationships: {},
@@ -319,6 +324,10 @@ const users = {
mutations, mutations,
getters, getters,
actions: { actions: {
setDisplayBackground (store, url) {
console.log("Performing user profile bg action...")
store.commit('setDisplayBackground', url)
},
fetchUserIfMissing (store, id) { fetchUserIfMissing (store, id) {
if (!store.getters.findUser(id)) { if (!store.getters.findUser(id)) {
store.dispatch('fetchUser', id) store.dispatch('fetchUser', id)
+6
View File
@@ -107,6 +107,7 @@ const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements' const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
const PLEROMA_EDIT_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}` const PLEROMA_EDIT_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
const PLEROMA_DELETE_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}` const PLEROMA_DELETE_ANNOUNCEMENT_URL = id => `/api/v1/pleroma/admin/announcements/${id}`
const PLEROMA_USER_FAVORITES_TIMELINE_URL = id => `/api/v1/pleroma/accounts/${id}/favourites`
const AKKOMA_SETTING_PROFILE_URL = (name) => `/api/v1/akkoma/frontend_settings/pleroma-fe/${name}` const AKKOMA_SETTING_PROFILE_URL = (name) => `/api/v1/akkoma/frontend_settings/pleroma-fe/${name}`
const AKKOMA_SETTING_PROFILE_LIST = `/api/v1/akkoma/frontend_settings/pleroma-fe` const AKKOMA_SETTING_PROFILE_LIST = `/api/v1/akkoma/frontend_settings/pleroma-fe`
const MASTODON_TAG_URL = (name) => `/api/v1/tags/${name}` const MASTODON_TAG_URL = (name) => `/api/v1/tags/${name}`
@@ -709,6 +710,7 @@ const fetchTimeline = ({
media: MASTODON_USER_TIMELINE_URL, media: MASTODON_USER_TIMELINE_URL,
list: MASTODON_LIST_TIMELINE_URL, list: MASTODON_LIST_TIMELINE_URL,
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
tag: MASTODON_TAG_TIMELINE_URL, tag: MASTODON_TAG_TIMELINE_URL,
bookmarks: MASTODON_BOOKMARK_TIMELINE_URL bookmarks: MASTODON_BOOKMARK_TIMELINE_URL
} }
@@ -717,6 +719,10 @@ const fetchTimeline = ({
let url = timelineUrls[timeline] let url = timelineUrls[timeline]
if (timeline === 'favorites' && userId) {
url = timelineUrls.publicFavorites(userId)
}
if (timeline === 'user' || timeline === 'media' || timeline === 'replies') { if (timeline === 'user' || timeline === 'media' || timeline === 'replies') {
url = url(userId) url = url(userId)
} }
@@ -95,6 +95,7 @@ export const parseUser = (data) => {
if (data.akkoma) { if (data.akkoma) {
output.instance = data.akkoma.instance output.instance = data.akkoma.instance
output.status_ttl_days = data.akkoma.status_ttl_days output.status_ttl_days = data.akkoma.status_ttl_days
output.permit_followback = data.akkoma.permit_followback
} }
if (data.pleroma) { if (data.pleroma) {
@@ -109,6 +110,7 @@ export const parseUser = (data) => {
output.allow_following_move = data.pleroma.allow_following_move output.allow_following_move = data.pleroma.allow_following_move
output.hide_favorites = data.pleroma.hide_favorites
output.hide_follows = data.pleroma.hide_follows output.hide_follows = data.pleroma.hide_follows
output.hide_followers = data.pleroma.hide_followers output.hide_followers = data.pleroma.hide_followers
output.hide_follows_count = data.pleroma.hide_follows_count output.hide_follows_count = data.pleroma.hide_follows_count
+24 -1
View File
@@ -8,7 +8,30 @@ const specialLanguageCodes = {
'zh': 'zh-Hans' 'zh': 'zh-Hans'
} }
const internalToBrowserLocale = code => specialLanguageCodes[code] || code // Find a browser language that matches the configured UI language.
// Browser language should match the configured generic short code prefix:
// eg 'en-GB' browser language matches 'en' UI language.
const findBrowserRegionMatch = genericLang => {
for (const blang of window.navigator.languages) {
if (genericLang === blang.split('-')[0])
return blang;
}
return null;
}
const internalToBrowserLocale = (() => {
const resolvedBrowserLocales = {}
return i18nLocale => {
if (resolvedBrowserLocales[i18nLocale]) {
return resolvedBrowserLocales[i18nLocale]
}
const lang = specialLanguageCodes[i18nLocale] || i18nLocale;
const resolved = findBrowserRegionMatch(lang) || lang;
resolvedBrowserLocales[i18nLocale] = resolved
return resolved
}
})()
const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-') const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
+2
View File
@@ -13,10 +13,12 @@
"logoMargin": ".1em", "logoMargin": ".1em",
"logoMask": true, "logoMask": true,
"logoLeft": false, "logoLeft": false,
"minimalScopesMode": false,
"nsfwCensorImage": "", "nsfwCensorImage": "",
"postContentType": "text/plain", "postContentType": "text/plain",
"redirectRootLogin": "/main/friends", "redirectRootLogin": "/main/friends",
"redirectRootNoLogin": "/main/all", "redirectRootNoLogin": "/main/all",
"scopeCopy": true,
"showFeaturesPanel": true, "showFeaturesPanel": true,
"showInstanceSpecificPanel": false, "showInstanceSpecificPanel": false,
"sidebarRight": false, "sidebarRight": false,