From 96ef76f1d76df7bd1e216524d5fe429830146aa6 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 18 Jun 2023 15:44:19 -0400 Subject: [PATCH 01/12] Make emoji picker buttons accessible --- src/components/emoji_picker/emoji_picker.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue index 6972164b02..3319dace95 100644 --- a/src/components/emoji_picker/emoji_picker.vue +++ b/src/components/emoji_picker/emoji_picker.vue @@ -22,6 +22,7 @@ active: activeGroupView === group.id }" :title="group.text" + role="button" @click.prevent="highlight(group.id)" > From 16140c278ba68f4ad9817181c943477a8b367858 Mon Sep 17 00:00:00 2001 From: tusooa Date: Sun, 18 Jun 2023 15:46:05 -0400 Subject: [PATCH 02/12] Add changelog --- changelog.d/emoji-picker-button-accessible.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/emoji-picker-button-accessible.fix diff --git a/changelog.d/emoji-picker-button-accessible.fix b/changelog.d/emoji-picker-button-accessible.fix new file mode 100644 index 0000000000..12898a1ac5 --- /dev/null +++ b/changelog.d/emoji-picker-button-accessible.fix @@ -0,0 +1 @@ +Add alt text to emoji picker buttons From 1c2048749480800e2d44b3876378beee421a79ba Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 20:45:44 -0400 Subject: [PATCH 03/12] Implement showing quotes --- src/components/status/status.js | 3 +++ src/components/status/status.scss | 6 ++++++ src/components/status/status.vue | 9 +++++++++ src/modules/statuses.js | 4 ++++ .../entity_normalizer/entity_normalizer.service.js | 4 ++++ 5 files changed, 26 insertions(+) diff --git a/src/components/status/status.js b/src/components/status/status.js index 9a9bca7aa0..c10c6cf5f5 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -401,6 +401,9 @@ const Status = { }, editingAvailable () { return this.$store.state.instance.editingAvailable + }, + quotedStatus () { + return this.status.quote_id ? this.$store.state.statuses.allStatusesObject[this.status.quote_id] : undefined } }, methods: { diff --git a/src/components/status/status.scss b/src/components/status/status.scss index 448128675b..4dfd1f87e9 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -422,4 +422,10 @@ } } } + + .quoted-status { + margin-top: 0.5em; + border: 1px solid var(--border, $fallback--border); + border-radius: var(--attachmentRadius, $fallback--attachmentRadius); + } } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 35b153627a..58fc0ecad6 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -364,6 +364,15 @@ @parseReady="setHeadTailLinks" /> +
+ +
+
{ output.thread_muted = pleroma.thread_muted output.emoji_reactions = pleroma.emoji_reactions output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible + output.quote = pleroma.quote ? parseStatus(pleroma.quote) : undefined + output.quote_id = output.quote ? output.quote.id : undefined + output.quote_url = pleroma.quote_url + output.quote_visible = pleroma.quote_visible } else { output.text = data.content output.summary = data.spoiler_text From d72486f3e4f2af9db95535f3f93e6257c675e155 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 12 Jul 2023 21:34:19 -0400 Subject: [PATCH 04/12] Implement sending quote posts --- .../post_status_form/post_status_form.js | 31 ++++++++++-- .../post_status_form/post_status_form.vue | 50 +++++++++++++++++++ src/i18n/en.json | 2 + src/services/api/api.service.js | 4 ++ .../status_poster/status_poster.service.js | 2 + 5 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index b75fee6911..7317613260 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -156,7 +156,8 @@ const PostStatusForm = { poll: this.statusPoll || {}, mediaDescriptions: this.statusMediaDescriptions || {}, visibility: this.statusScope || scope, - contentType: statusContentType + contentType: statusContentType, + quoting: false } } @@ -265,6 +266,24 @@ const PostStatusForm = { isEdit () { return typeof this.statusId !== 'undefined' && this.statusId.trim() !== '' }, + quotable () { + if (!this.replyTo) { + return false + } + + const repliedStatus = this.$store.state.statuses.allStatusesObject[this.replyTo] + if (!repliedStatus) { + return false + } + + if (repliedStatus.visibility === 'public' || + repliedStatus.visibility === 'unlisted' || + repliedStatus.visibility === 'local') { + return true + } else if (repliedStatus.visibility === 'private') { + return repliedStatus.account.id === this.$store.state.users.currentUser.id + } + }, ...mapGetters(['mergedConfig']), ...mapState({ mobileLayout: state => state.interface.mobileLayout @@ -292,7 +311,8 @@ const PostStatusForm = { visibility: newStatus.visibility, contentType: newStatus.contentType, poll: {}, - mediaDescriptions: {} + mediaDescriptions: {}, + quoting: false } this.pollFormVisible = false this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile() @@ -340,6 +360,8 @@ const PostStatusForm = { return } + const replyOrQuoteAttr = newStatus.quoting ? 'quoteId' : 'inReplyToStatusId' + const postingOptions = { status: newStatus.status, spoilerText: newStatus.spoilerText || null, @@ -347,7 +369,7 @@ const PostStatusForm = { sensitive: newStatus.nsfw, media: newStatus.files, store: this.$store, - inReplyToStatusId: this.replyTo, + [replyOrQuoteAttr]: this.replyTo, contentType: newStatus.contentType, poll, idempotencyKey: this.idempotencyKey @@ -373,6 +395,7 @@ const PostStatusForm = { } const newStatus = this.newStatus this.previewLoading = true + const replyOrQuoteAttr = newStatus.quoting ? 'quoteId' : 'inReplyToStatusId' statusPoster.postStatus({ status: newStatus.status, spoilerText: newStatus.spoilerText || null, @@ -380,7 +403,7 @@ const PostStatusForm = { sensitive: newStatus.nsfw, media: [], store: this.$store, - inReplyToStatusId: this.replyTo, + [replyOrQuoteAttr]: this.replyTo, contentType: newStatus.contentType, poll: {}, preview: true diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 86c1f9073f..e06b88b2eb 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -126,6 +126,42 @@ class="preview-status" />
+
+ + +
Date: Thu, 13 Jul 2023 00:37:57 -0400 Subject: [PATCH 05/12] Handle recursive quotes --- .../post_status_form/post_status_form.js | 2 +- src/components/status/status.js | 25 ++++++++++++++- src/components/status/status.scss | 12 +++++++ src/components/status/status.vue | 32 ++++++++++++++++++- src/i18n/en.json | 5 ++- .../entity_normalizer.service.js | 2 +- 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 7317613260..1735099899 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -281,7 +281,7 @@ const PostStatusForm = { repliedStatus.visibility === 'local') { return true } else if (repliedStatus.visibility === 'private') { - return repliedStatus.account.id === this.$store.state.users.currentUser.id + return repliedStatus.user.id === this.$store.state.users.currentUser.id } }, ...mapGetters(['mergedConfig']), diff --git a/src/components/status/status.js b/src/components/status/status.js index c10c6cf5f5..e722a635e8 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -133,6 +133,7 @@ const Status = { 'showPinned', 'inProfile', 'profileUserId', + 'inQuote', 'simpleTree', 'controlledThreadDisplayStatus', @@ -159,7 +160,8 @@ const Status = { uncontrolledMediaPlaying: [], suspendable: true, error: null, - headTailLinks: null + headTailLinks: null, + displayQuote: !this.inQuote } }, computed: { @@ -402,8 +404,17 @@ const Status = { editingAvailable () { return this.$store.state.instance.editingAvailable }, + hasVisibleQuote () { + return this.status.quote_url && this.status.quote_visible + }, + hasInvisibleQuote () { + return this.status.quote_url && !this.status.quote_visible + }, quotedStatus () { return this.status.quote_id ? this.$store.state.statuses.allStatusesObject[this.status.quote_id] : undefined + }, + shouldDisplayQuote () { + return this.quotedStatus && this.displayQuote } }, methods: { @@ -472,6 +483,18 @@ const Status = { window.scrollBy(0, rect.bottom - window.innerHeight + 50) } } + }, + toggleDisplayQuote () { + if (this.shouldDisplayQuote) { + this.displayQuote = false + } else if (!this.quotedStatus) { + this.$store.dispatch('fetchStatus', this.status.quote_id) + .then(() => { + this.displayQuote = true + }) + } else { + this.displayQuote = true + } } }, watch: { diff --git a/src/components/status/status.scss b/src/components/status/status.scss index 4dfd1f87e9..760c6ac1af 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -427,5 +427,17 @@ margin-top: 0.5em; border: 1px solid var(--border, $fallback--border); border-radius: var(--attachmentRadius, $fallback--attachmentRadius); + + &.-unavailable-prompt { + padding: 0.5em; + } + } + + .display-quoted-status-button { + margin: 0.5em; + + &-icon { + color: inherit; + } } } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 58fc0ecad6..c49a9e7b4f 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -365,13 +365,43 @@ />
+
+

+ + + +

{ output.emoji_reactions = pleroma.emoji_reactions output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible output.quote = pleroma.quote ? parseStatus(pleroma.quote) : undefined - output.quote_id = output.quote ? output.quote.id : undefined + output.quote_id = pleroma.quote_id ? pleroma.quote_id : (output.quote ? output.quote.id : undefined) output.quote_url = pleroma.quote_url output.quote_visible = pleroma.quote_visible } else { From 63f56cfb274165e157378888f5d448bfb194a52f Mon Sep 17 00:00:00 2001 From: tusooa Date: Thu, 13 Jul 2023 00:43:55 -0400 Subject: [PATCH 06/12] Add aria-labels for reply/quote selector --- src/components/post_status_form/post_status_form.js | 1 + src/components/post_status_form/post_status_form.vue | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 1735099899..65066d542a 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -162,6 +162,7 @@ const PostStatusForm = { } return { + randomSeed: `${Math.random()}`.replace('.', '-'), dropFiles: [], uploadingFiles: false, error: null, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index e06b88b2eb..0d79388544 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -135,6 +135,7 @@ class="reply-or-quote-option" tabindex="0" role="radio" + :aria-labelledby="`reply-or-quote-option-${randomSeed}-reply`" :aria-checked="!newStatus.quoting" @click="newStatus.quoting = false" > @@ -143,13 +144,16 @@ :checked="!newStatus.quoting" >
From 99cff7e28b6f99620e18936af8b4fe8f725eb156 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 17 Jul 2023 11:29:36 -0400 Subject: [PATCH 07/12] Add changelog for quote --- changelog.d/quote.add | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/quote.add diff --git a/changelog.d/quote.add b/changelog.d/quote.add new file mode 100644 index 0000000000..b43b6abad3 --- /dev/null +++ b/changelog.d/quote.add @@ -0,0 +1 @@ +Implement quoting From 441eea36837a132b841d6c7423e146990b48ab16 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 17 Jul 2023 17:28:36 -0400 Subject: [PATCH 08/12] Do not show quote options for servers without quote support --- src/boot/after_store.js | 1 + src/components/post_status_form/post_status_form.js | 4 ++++ src/modules/instance.js | 1 + 3 files changed, 6 insertions(+) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 9c1f007bd5..395d483449 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -259,6 +259,7 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) + store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) const uploadLimits = metadata.uploadLimits store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 65066d542a..97d3cb2299 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -268,6 +268,10 @@ const PostStatusForm = { return typeof this.statusId !== 'undefined' && this.statusId.trim() !== '' }, quotable () { + if (!this.$store.state.instance.quotingAvailable) { + return false + } + if (!this.replyTo) { return false } diff --git a/src/modules/instance.js b/src/modules/instance.js index bb0292da0b..1ee64552e6 100644 --- a/src/modules/instance.js +++ b/src/modules/instance.js @@ -128,6 +128,7 @@ const defaultState = { mediaProxyAvailable: false, suggestionsEnabled: false, suggestionsWeb: '', + quotingAvailable: false, // Html stuff instanceSpecificPanelContent: '', From a314ad7ccca05c91d4a717d956c207a0b0c47005 Mon Sep 17 00:00:00 2001 From: tusooa Date: Mon, 17 Jul 2023 17:29:18 -0400 Subject: [PATCH 09/12] Add fallback return false for quotable judgements --- src/components/post_status_form/post_status_form.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 97d3cb2299..ba49961d78 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -288,6 +288,8 @@ const PostStatusForm = { } else if (repliedStatus.visibility === 'private') { return repliedStatus.user.id === this.$store.state.users.currentUser.id } + + return false }, ...mapGetters(['mergedConfig']), ...mapState({ From 35d3b8f27dace6efd5aab8915b2025d16bb7e7e7 Mon Sep 17 00:00:00 2001 From: tusooa Date: Tue, 18 Jul 2023 19:06:00 -0400 Subject: [PATCH 10/12] Fix pinned statuses gone when reloading user timeline --- changelog.d/reload-user-pinned.fix | 1 + src/components/timeline/timeline.js | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 changelog.d/reload-user-pinned.fix diff --git a/changelog.d/reload-user-pinned.fix b/changelog.d/reload-user-pinned.fix new file mode 100644 index 0000000000..db241c205a --- /dev/null +++ b/changelog.d/reload-user-pinned.fix @@ -0,0 +1 @@ +Fix pinned statuses gone when reloading user timeline diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index b74146109b..1050b87a64 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -160,6 +160,9 @@ const Timeline = { if (this.timeline.flushMarker !== 0) { this.$store.commit('clearTimeline', { timeline: this.timelineName, excludeUserId: true }) this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 }) + if (this.timelineName === 'user') { + this.$store.dispatch('fetchPinnedStatuses', this.userId) + } this.fetchOlderStatuses() } else { this.blockClicksTemporarily() From 1b081a927288ff2f43025d688e2fa15cf6ce6be1 Mon Sep 17 00:00:00 2001 From: tusooa Date: Wed, 19 Jul 2023 17:00:46 -0400 Subject: [PATCH 11/12] Make reply-or-quote selection display as horizontal button group --- .../post_status_form/post_status_form.vue | 50 ++++++------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 0d79388544..9b108a5aa1 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -129,44 +129,32 @@
- -
+
+ {{ $t('post_status.quote_option') }} +
Date: Fri, 21 Jul 2023 13:54:10 -0400 Subject: [PATCH 12/12] Fix parsing non-ascii tags --- changelog.d/nonascii-tags.fix | 1 + src/services/matcher/matcher.service.js | 7 +++++-- test/unit/specs/services/matcher/matcher.spec.js | 6 ++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 changelog.d/nonascii-tags.fix diff --git a/changelog.d/nonascii-tags.fix b/changelog.d/nonascii-tags.fix new file mode 100644 index 0000000000..e4c6dc82d3 --- /dev/null +++ b/changelog.d/nonascii-tags.fix @@ -0,0 +1 @@ +Fix parsing non-ascii tags diff --git a/src/services/matcher/matcher.service.js b/src/services/matcher/matcher.service.js index b6c4e90996..54f02d3125 100644 --- a/src/services/matcher/matcher.service.js +++ b/src/services/matcher/matcher.service.js @@ -14,8 +14,11 @@ export const mentionMatchesUrl = (attention, url) => { * @param {string} url */ export const extractTagFromUrl = (url) => { - const regex = /tag[s]*\/(\w+)$/g - const result = regex.exec(url) + const decoded = decodeURI(url) + // https://git.pleroma.social/pleroma/elixir-libraries/linkify/-/blob/master/lib/linkify/parser.ex + // https://www.pcre.org/original/doc/html/pcrepattern.html + const regex = /tag[s]*\/([\p{L}\p{N}_]*[\p{Alphabetic}_·\u{200c}][\p{L}\p{N}_·\p{M}\u{200c}]*)$/ug + const result = regex.exec(decoded) if (!result) { return false } diff --git a/test/unit/specs/services/matcher/matcher.spec.js b/test/unit/specs/services/matcher/matcher.spec.js index 7a2494f074..c6e9719d3d 100644 --- a/test/unit/specs/services/matcher/matcher.spec.js +++ b/test/unit/specs/services/matcher/matcher.spec.js @@ -78,5 +78,11 @@ describe('MatcherService', () => { expect(MatcherService.extractTagFromUrl(url)).to.eql(false) }) + + it('should return tag name from non-ascii tags', () => { + const url = encodeURI('https://website.com/tag/喵喵喵') + + expect(MatcherService.extractTagFromUrl(url)).to.eql('喵喵喵') + }) }) })