From 413ecf30bc6835b7ad209f5c0d90a78e393ebb96 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 12 May 2026 22:15:48 +0300 Subject: [PATCH 1/9] initial MFM support --- src/components/rich_content/rich_content.jsx | 83 ++++- src/components/rich_content/rich_content.scss | 291 ++++++++++++++++++ 2 files changed, 373 insertions(+), 1 deletion(-) diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 95ab102c6..322b4a8fa 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -313,7 +313,88 @@ export default { const newChildren = Array.isArray(children) ? [...children].reverse().map(processItemReverse).reverse() : children - return {newChildren} + const attrs = getAttrs(opener) + const newAttrs = { ...attrs } + const fullAttrs = getAttrs(opener, () => true) + const classname = fullAttrs['class'] + const isMFM = classname?.startsWith('mfm-') + if (isMFM) { + const mfmOperator = /^mfm-(\w+)$/.exec(classname)?.[1] + newAttrs['class'] = 'mfm' + newAttrs['data-mfm-operator'] = mfmOperator + switch(mfmOperator) { + case 'position': { + const x = fullAttrs['data-mfm-x'] || 0 + const y = fullAttrs['data-mfm-y'] || 0 + newAttrs.style = [ + 'transform:', + `translateX(calc(${x} * (var(--emoji-size) / 2)))`, + `translateY(calc(${y} * (var(--emoji-size) / 2)))`, + ].join(' ') + break + } + case 'scale': { + const x = fullAttrs['data-mfm-x'] || 1 + const y = fullAttrs['data-mfm-y'] || 1 + newAttrs.style = [ + 'transform:', + `scale(${x}, ${y})`, + ].join(' ') + break + } + case 'rotate': { + const deg = fullAttrs['data-mfm-deg'] || 0 + newAttrs.style = [ + 'transform:', + `rotate(${deg}deg);`, + 'transform-origin:', + 'center', + ].join(' ') + break + } + case 'bg': { + const color = fullAttrs['data-mfm-color'] || 0 + newAttrs.style = [ + `background-color: #${color}`, + ].join(' ') + break + } + case 'fg': { + const color = fullAttrs['data-mfm-color'] || 0 + newAttrs.style = [ + `color: #${color}`, + ].join(' ') + break + } + case 'spin': { + const speed = fullAttrs['data-mfm-speed'] + const y = fullAttrs['data-mfm-y'] != null + const x = fullAttrs['data-mfm-x'] != null + const anim = [ + x ? 'mfm-spinX' : null, + y ? 'mfm-spinY' : null, + 'mfm-spin' + ].filter(a => a)[0] + newAttrs.style = `animation: ${speed} linear 3s infinite normal none running ${anim}` + break + } + case 'flip': { + newAttrs.style = 'transform: scaleX(-1)' + break + } + case 'jump': + case 'twitch': + case 'shake': + case 'bounce': + newAttrs.style = `animation: 0.75s linear 0s infinite normal none running mfm-${mfmOperator}` + break + default: + console.log(mfmOperator, opener) + console.log(mfmOperator) + break + } + } + return {newChildren} } else { return } diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index 33effc623..230ea8be5 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -1,6 +1,11 @@ .RichContent { font-family: var(--font); + .mfm { + display: inline-block; + font-size: calc(var(--emoji-size) / 2); + } + &.-faint { color: var(--text); /* stylelint-disable declaration-no-important */ @@ -106,3 +111,289 @@ a .RichContent { /* stylelint-disable-next-line declaration-no-important */ color: var(--link) !important; } + +@keyframes mfm-spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes mfm-spinX { + 0% { + transform: perspective(8em) rotateX(0); + } + + 100% { + transform: perspective(8em) rotateX(360deg); + } +} + +@keyframes mfm-spinY { + 0% { + transform: perspective(8em) rotateY(0); + } + + 100% { + transform: perspective(8em) rotateY(360deg); + } +} + +@keyframes mfm-jump { + 0% { + transform: translateY(0) + } + + 25% { + transform: translateY(-16px) + } + + 50% { + transform: translateY(0) + } + + 75% { + transform: translateY(-8px) + } + + 100% { + transform: translateY(0) + } +} + +@keyframes mfm-bounce { + 0% { + transform: translateY(0) scale(1) + } + + 25% { + transform: translateY(-16px) scale(1) + } + + 50% { + transform: translateY(0) scale(1) + } + + 75% { + transform: translateY(0) scale(1.5,.75) + } + + 100% { + transform: translateY(0) scale(1) + } +} + +@keyframes mfm-twitch { + 0% { + transform: translate(7px,-2px) + } + + 5% { + transform: translate(-3px,1px) + } + + 10% { + transform: translate(-7px,-1px) + } + + 15% { + transform: translateY(-1px) + } + + 20% { + transform: translate(-8px,6px) + } + + 25% { + transform: translate(-4px,-3px) + } + + 30% { + transform: translate(-4px,-6px) + } + + 35% { + transform: translate(-8px,-8px) + } + + 40% { + transform: translate(4px,6px) + } + + 45% { + transform: translate(-3px,1px) + } + + 50% { + transform: translate(2px,-10px) + } + + 55% { + transform: translate(-7px) + } + + 60% { + transform: translate(-2px,4px) + } + + 65% { + transform: translate(3px,-8px) + } + + 70% { + transform: translate(6px,7px) + } + + 75% { + transform: translate(-7px,-2px) + } + + 80% { + transform: translate(-7px,-8px) + } + + 85% { + transform: translate(9px,3px) + } + + 90% { + transform: translate(-3px,-2px) + } + + 95% { + transform: translate(-10px,2px) + } + + 100% { + transform: translate(-2px,-6px) + } +} + +@keyframes mfm-shake { + 0% { + transform: translate(-3px,-1px) rotate(-8deg) + } + + 5% { + transform: translateY(-1px) rotate(-10deg) + } + + 10% { + transform: translate(1px,-3px) rotate(0) + } + + 15% { + transform: translate(1px,1px) rotate(11deg) + } + + 20% { + transform: translate(-2px,1px) rotate(1deg) + } + + 25% { + transform: translate(-1px,-2px) rotate(-2deg) + } + + 30% { + transform: translate(-1px,2px) rotate(-3deg) + } + + 35% { + transform: translate(2px,1px)rotate(6deg) + } + + 40% { + transform: translate(-2px,-3px)rotate(-9deg) + } + + 45% { + transform: translateY(-1px)rotate(-12deg) + } + + 50% { + transform: translate(1px,2px)rotate(10deg) + } + + 55% { + transform: translateY(-3px)rotate(8deg) + } + + 60% { + transform: translate(1px,-1px)rotate(8deg) + } + + 65% { + transform: translateY(-1px)rotate(-7deg) + } + + 70% { + transform: translate(-1px,-3px)rotate(6deg) + } + + 75% { + transform: translateY(-2px)rotate(4deg) + } + + 80% { + transform: translate(-2px,-1px)rotate(3deg) + } + + 85% { + transform: translate(1px,-3px)rotate(-10deg) + } + + 90% { + transform: translate(1px)rotate(3deg) + } + + 95% { + transform: translate(-2px)rotate(-3deg) + } + + 100% { + transform: translate(2px,1px)rotate(2deg) + } +} + +@keyframes mfm-rubberBand { + 0% { + transform: scale(1) + } + + 30% { + transform: scale(1.25,.75) + } + + 40% { + transform: scale(.75,1.25) + } + + 50% { + transform: scale(1.15,.85) + } + + 65% { + transform: scale(.95,1.05) + } + + 75% { + transform: scale(1.05,.95) + } + + 100% { + transform: scale(1) + } +} + +@keyframes mfm-rainbow { + 0% { + filter: hue-rotate()contrast(150%)saturate(150%) + } + + 100% { + filter: hue-rotate(360deg)contrast(150%)saturate(150%) + } +} From 5e7fef8c7376bbbb04366d49a6955879fe9ed9ea Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 1 Jun 2026 22:24:11 +0300 Subject: [PATCH 2/9] delay --- src/components/rich_content/rich_content.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 322b4a8fa..60b546a83 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -367,7 +367,8 @@ export default { break } case 'spin': { - const speed = fullAttrs['data-mfm-speed'] + const speed = fullAttrs['data-mfm-speed'] = '1s' + const delay = fullAttrs['data-mfm-delay'] = 0 const y = fullAttrs['data-mfm-y'] != null const x = fullAttrs['data-mfm-x'] != null const anim = [ @@ -375,7 +376,7 @@ export default { y ? 'mfm-spinY' : null, 'mfm-spin' ].filter(a => a)[0] - newAttrs.style = `animation: ${speed} linear 3s infinite normal none running ${anim}` + newAttrs.style = `animation: ${speed} linear ${delay} infinite normal none running ${anim}` break } case 'flip': { From 4934feb33e2d0423fb50876062669e0f6efe6857 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 2 Jun 2026 16:30:38 +0300 Subject: [PATCH 3/9] =?UTF-8?q?=E8=B8=8A=E3=81=A3=E3=81=A6=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=A4=9C=E3=82=92=E7=9F=A5=E3=82=89=E3=81=AA=E3=81=84?= =?UTF-8?q?=20=E8=B8=8A=E3=81=A3=E3=81=A6=E3=81=AA=E3=81=84=E5=A4=9C?= =?UTF-8?q?=E3=81=8C=E6=B0=97=E3=81=AB=E5=85=A5=E3=82=89=E3=81=AA=E3=81=84?= =?UTF-8?q?=20https://shigusegubu.club/notice/B6Y2J5biTNp1VtN0pE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/rich_content/rich_content.jsx | 81 ++++++++++++++----- src/components/rich_content/rich_content.scss | 11 ++- 2 files changed, 72 insertions(+), 20 deletions(-) diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index 60b546a83..1625f4a51 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -324,18 +324,18 @@ export default { newAttrs['data-mfm-operator'] = mfmOperator switch(mfmOperator) { case 'position': { - const x = fullAttrs['data-mfm-x'] || 0 - const y = fullAttrs['data-mfm-y'] || 0 + const x = fullAttrs['data-mfm-x'].replace(/\.+$/,'') || 0 + const y = fullAttrs['data-mfm-y'].replace(/\.+$/,'') || 0 newAttrs.style = [ 'transform:', - `translateX(calc(${x} * (var(--emoji-size) / 2)))`, - `translateY(calc(${y} * (var(--emoji-size) / 2)))`, + `translate(calc(${x} * (var(--emoji-size) / 2)), `, + `calc(${y} * (var(--emoji-size) / 2)))`, ].join(' ') break } case 'scale': { - const x = fullAttrs['data-mfm-x'] || 1 - const y = fullAttrs['data-mfm-y'] || 1 + const x = fullAttrs['data-mfm-x'].replace(/\.+$/,'') || 1 + const y = fullAttrs['data-mfm-y'].replace(/\.+$/,'') || 1 newAttrs.style = [ 'transform:', `scale(${x}, ${y})`, @@ -345,11 +345,9 @@ export default { case 'rotate': { const deg = fullAttrs['data-mfm-deg'] || 0 newAttrs.style = [ - 'transform:', - `rotate(${deg}deg);`, - 'transform-origin:', - 'center', - ].join(' ') + `transform: rotate(${deg}deg)`, + 'transform-origin: center', + ].join(';') break } case 'bg': { @@ -363,32 +361,79 @@ export default { const color = fullAttrs['data-mfm-color'] || 0 newAttrs.style = [ `color: #${color}`, - ].join(' ') + ].join(';') break } case 'spin': { - const speed = fullAttrs['data-mfm-speed'] = '1s' - const delay = fullAttrs['data-mfm-delay'] = 0 + const speed = fullAttrs['data-mfm-speed'] || '1s' + const delay = fullAttrs['data-mfm-delay'] || 0 + const left = fullAttrs['data-mfm-left'] != null + const alternate = fullAttrs['data-mfm-alternate'] != null const y = fullAttrs['data-mfm-y'] != null const x = fullAttrs['data-mfm-x'] != null + const anim = [ x ? 'mfm-spinX' : null, y ? 'mfm-spinY' : null, 'mfm-spin' ].filter(a => a)[0] - newAttrs.style = `animation: ${speed} linear ${delay} infinite normal none running ${anim}` + + const direction = [ + alternate ? 'alternate' : null, + left ? 'reverse' : null, + 'normal', + ].filter(a => a)[0] + + newAttrs.style = [ + `animation-name: ${anim}`, + `animation-duration: ${speed}`, + 'animation-iteration-count: infinite', + `animation-delay: ${delay}`, + `animation-direction: ${direction}`, + 'animation-fill-mode: none', + 'animation-timing-function: linear', + ].join(';') break } case 'flip': { newAttrs.style = 'transform: scaleX(-1)' break } - case 'jump': + case 'border': { + const width = fullAttrs['data-mfm-width'] || '0' + const style = fullAttrs['data-mfm-style'] || 'solid' + const color = fullAttrs['data-mfm-color'] || 'transparent' + const radius = fullAttrs['data-mfm-radius'] || '0' + const noclip = fullAttrs['data-mfm-noclip'] || false + + newAttrs.style = [ + `border: ${width} ${style} ${color}`, + `border-radius: ${radius}`, + `overflow: ${noclip ? 'visible' : 'clip'}` + ].join(';') + break + } + case 'tada': + case 'jelly': case 'twitch': case 'shake': - case 'bounce': - newAttrs.style = `animation: 0.75s linear 0s infinite normal none running mfm-${mfmOperator}` + case 'jump': + case 'bounce': { + const speed = fullAttrs['data-mfm-speed'] || '1s' + const delay = fullAttrs['data-mfm-delay'] || 0 + + const rules = [ + `animation-name: mfm-${mfmOperator}`, + `animation-duration: ${speed}`, + 'animation-iteration-count: infinite', + `animation-delay: ${delay}`, + 'animation-direction: normal', + 'animation-fill-mode: none', + 'animation-timing-function: linear', + ].join(';') + newAttrs.style = rules break + } default: console.log(mfmOperator, opener) console.log(mfmOperator) diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index 230ea8be5..4aad49477 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -1,9 +1,16 @@ .RichContent { font-family: var(--font); + text-align: center; .mfm { display: inline-block; font-size: calc(var(--emoji-size) / 2); + + .emoji { + /* Misskey's emoji width knows no bounds */ + /* stylelint-disable-next-line declaration-no-important */ + max-width: unset !important; + } } &.-faint { @@ -148,7 +155,7 @@ a .RichContent { } 25% { - transform: translateY(-16px) + transform: translateY(-1em) } 50% { @@ -156,7 +163,7 @@ a .RichContent { } 75% { - transform: translateY(-8px) + transform: translateY(-0.5em) } 100% { From a5179b6381f1ebad0d00b5aa9a89e3a4574e7795 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 2 Jun 2026 16:34:57 +0300 Subject: [PATCH 4/9] undo center --- src/components/rich_content/rich_content.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss index 4aad49477..f048e529e 100644 --- a/src/components/rich_content/rich_content.scss +++ b/src/components/rich_content/rich_content.scss @@ -1,6 +1,5 @@ .RichContent { font-family: var(--font); - text-align: center; .mfm { display: inline-block; From 6c9b4a48d8330e93b253401a37e7861e67f6dd79 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 6 Jul 2026 19:08:51 +0300 Subject: [PATCH 5/9] lint --- src/components/error_modal/error_modal.vue | 5 ++++- src/components/global_error/global_error.vue | 5 ++++- src/components/palette_editor/palette_editor.vue | 6 ++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/error_modal/error_modal.vue b/src/components/error_modal/error_modal.vue index 7429e8bdc..ef2654b2e 100644 --- a/src/components/error_modal/error_modal.vue +++ b/src/components/error_modal/error_modal.vue @@ -25,7 +25,10 @@
{{ $t('general.generic_error_details') }} - +
diff --git a/src/components/global_error/global_error.vue b/src/components/global_error/global_error.vue index 3c3901d4f..2b6e83655 100644 --- a/src/components/global_error/global_error.vue +++ b/src/components/global_error/global_error.vue @@ -13,7 +13,10 @@
{{ $t('general.generic_error_details') }} - +
diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue index 5b89f2af2..8fe6ba5c4 100644 --- a/src/components/palette_editor/palette_editor.vue +++ b/src/components/palette_editor/palette_editor.vue @@ -4,9 +4,11 @@ :class="{ '-compact': compact, '-apply': apply, '-mobile': mobile }" >
-
+
Date: Mon, 6 Jul 2026 19:43:08 +0300 Subject: [PATCH 6/9] fix and refactor the way status focusing work --- src/components/conversation/conversation.js | 27 +++++++++----------- src/components/conversation/conversation.vue | 15 +++++------ src/components/status/status.js | 27 ++++++-------------- src/components/status/status.vue | 7 +++-- src/components/thread_tree/thread_tree.js | 4 +-- src/components/thread_tree/thread_tree.vue | 7 ++--- 6 files changed, 32 insertions(+), 55 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 1bc9f3858..00605657e 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -97,7 +97,7 @@ const conversation = { }, data() { return { - highlight: null, + focused: null, expanded: false, threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' inlineDivePosition: null, @@ -389,6 +389,9 @@ const conversation = { canDive() { return this.isTreeView && this.isExpanded }, + maybeFocused() { + return this.isExpanded ? this.focused : null + }, ...mapPiniaState(useMergedConfigStore, ['mergedConfig']), ...mapState({ mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus, @@ -411,7 +414,7 @@ const conversation = { oldConversationId && newConversationId === oldConversationId ) { - this.setHighlight(this.originalStatusId) + this.setFocused(this.originalStatusId) } else { this.fetchConversation() } @@ -439,7 +442,7 @@ const conversation = { }).then(({ data: { ancestors, descendants } }) => { this.$store.dispatch('addNewStatuses', { statuses: ancestors }) this.$store.dispatch('addNewStatuses', { statuses: descendants }) - this.setHighlight(this.originalStatusId) + this.setFocused(this.originalStatusId) }) } else { this.loadStatusError = null @@ -457,18 +460,12 @@ const conversation = { }) } }, - isFocused(id) { - return this.isExpanded && id === this.highlight - }, getReplies(id) { return this.replies[id] || [] }, - maybeHighlight() { - return this.isExpanded ? this.highlight : null - }, - setHighlight(id) { + setFocused(id) { if (!id) return - this.highlight = id + this.focused = id if (!this.streamingEnabled) { this.$store.dispatch('fetchStatus', id) @@ -533,7 +530,7 @@ const conversation = { // only used when we are not on a page undive() { this.inlineDivePosition = null - this.setHighlight(this.statusId) + this.setFocused(this.statusId) }, tryScrollTo(id) { if (!id) { @@ -551,7 +548,7 @@ const conversation = { // contain scrolling calls, as we do not want the page to jump // when we scroll with an expanded conversation. // - // Now the method is to rely solely on the `highlight` watcher + // Now the method is to rely solely on the `focused` watcher // in `status` components. // In linear views, all statuses are rendered at all times, but // in tree views, it is possible that a change in active status @@ -559,9 +556,9 @@ const conversation = { // status becomes an ancestor status, and thus they will be // different). // Here, let the components be rendered first, in order to trigger - // the `highlight` watcher. + // the `focused` watcher. this.$nextTick(() => { - this.setHighlight(id) + this.setFocused(id) }) }, goToCurrent() { diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 9bd8a96f9..684a5de92 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -96,8 +96,7 @@ :replies="getReplies(status.id)" :expandable="!isExpanded" - :focused="isFocused(status.id)" - :highlight="maybeHighlight" + :focused="maybeFocused === status.id" :inline-expanded="collapsable && isExpanded" :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]" :in-profile="inProfile" @@ -107,7 +106,7 @@ :show-other-replies-as-button="showOtherRepliesButtonInsideStatus" can-dive - @goto="setHighlight" + @goto="setFocused" @dive="() => diveIntoStatus(status.id)" @suspendable-state-change="onStatusSuspendStateChange" /> @@ -154,9 +153,8 @@ :pinned-status-ids-object="pinnedStatusIdsObject" :profile-user-id="profileUserId" - :is-focused-function="isFocused" :get-replies="getReplies" - :highlight="maybeHighlight" + :focused="maybeFocused" :toggle-expanded="toggleExpanded" :simple="treeViewIsSimple" @@ -166,7 +164,7 @@ :total-reply-depth="totalReplyDepth" :can-dive="canDive" - @goto="setHighlight" + @goto="setFocused" @dive="diveIntoStatus" @suspendable-state-change="onStatusSuspendStateChange" /> @@ -185,15 +183,14 @@ :replies="getReplies(status.id)" :expandable="!isExpanded" - :focused="isFocused(status.id)" - :highlight="maybeHighlight === status.id" + :focused="maybeFocused === status.id || maybeFocused === status.retweeted_status?.id" :inline-expanded="collapsable && isExpanded" :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]" :in-profile="inProfile" :in-conversation="isExpanded" :profile-user-id="profileUserId" - @goto="setHighlight" + @goto="setFocused" @toggle-expanded="toggleExpanded" @suspendable-state-change="onStatusSuspendStateChange" /> diff --git a/src/components/status/status.js b/src/components/status/status.js index 0cfc7cdba..996dc6437 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -97,7 +97,6 @@ const Status = { expandable: Boolean, focused: Boolean, - highlight: Boolean, compact: Boolean, isPreview: Boolean, noHeading: Boolean, @@ -320,7 +319,7 @@ const Status = { }, shouldNotMute() { if (this.ignoreMute) return true - if (this.isFocused) return true + if (this.focused) return true const { status } = this const { reblog } = status return ( @@ -357,16 +356,6 @@ const Status = { this.muteFilterHits.some((x) => x.hide)) ) }, - isFocused() { - // retweet or root of an expanded conversation - if (this.focused) { - return true - } else if (!this.inConversation) { - return false - } - // use conversation highlight only when in conversation - return this.status.id === this.highlight - }, isReply() { return !!( this.status.in_reply_to_status_id && this.status.in_reply_to_user_id @@ -415,7 +404,7 @@ const Status = { shouldDisplayFavsAndRepeats() { return ( !this.hidePostStats && - this.isFocused && + this.focused && (this.combinedFavsAndRepeatsUsers.length > 0 || this.statusFromGlobalRepository.quotes_count) ) @@ -558,9 +547,9 @@ const Status = { toggleThreadDisplay() { this.controlledToggleThreadDisplay() }, - scrollIfHighlighted(highlightId) { + scrollIfFocused(focusedId) { if (this.$el.getBoundingClientRect == null) return - const id = highlightId + const id = focusedId if (this.status.id === id) { const rect = this.$el.getBoundingClientRect() if (rect.top < 100) { @@ -577,13 +566,13 @@ const Status = { }, }, watch: { - highlight: function (id) { - this.scrollIfHighlighted(id) + focused: function (id) { + this.scrollIfFocused(id) }, 'status.repeat_num': function (num) { // refetch repeats when repeat_num is changed in any way if ( - this.isFocused && + this.focused && this.statusFromGlobalRepository.rebloggedBy && this.statusFromGlobalRepository.rebloggedBy.length !== num ) { @@ -593,7 +582,7 @@ const Status = { 'status.fave_num': function (num) { // refetch favs when fave_num is changed in any way if ( - this.isFocused && + this.focused && this.statusFromGlobalRepository.favoritedBy && this.statusFromGlobalRepository.favoritedBy.length !== num ) { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index f301ad890..4dcc1e07d 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -3,7 +3,7 @@ v-if="!hideStatus" ref="root" class="Status" - :class="[{ '-focused': isFocused }, { '-conversation': inlineExpanded }]" + :class="[{ '-focused': focused }, { '-conversation': inlineExpanded }]" >
diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 7631d127f..75232a259 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -19,10 +19,8 @@ const ThreadTree = { pinnedStatusIdsObject: Object, profileUserId: String, - isFocusedFunction: Function, - highlight: String, + focused: String, getReplies: Function, - setHighlight: Function, toggleExpanded: Function, simple: Boolean, diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index 72f8512e5..556858ad7 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -8,9 +8,8 @@ :inline-expanded="collapsable && isExpanded" :expandable="!isExpanded" :show-pinned="pinnedStatusIdsObject && pinnedStatusIdsObject[status.id]" - :focused="isFocusedFunction(status.id)" :in-conversation="isExpanded" - :highlight="highlight === status.id" + :focused="focused === status.id || focused === status.retweeted_status?.id" :in-profile="inProfile" :profile-user-id="profileUserId" class="conversation-status conversation-status-treeview status-fadein panel-body" @@ -42,10 +41,8 @@ :pinned-status-ids-object="pinnedStatusIdsObject" :profile-user-id="profileUserId" - :is-focused-function="isFocusedFunction" :get-replies="getReplies" - :highlight="highlight" - :set-highlight="setHighlight" + :focused="focused" :toggle-expanded="toggleExpanded" :simple="simple" From 72d5307fd9b9b8bf4f079217bde8280e206e150d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 6 Jul 2026 19:43:20 +0300 Subject: [PATCH 7/9] fix palette editor --- src/components/palette_editor/palette_editor.vue | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue index 8fe6ba5c4..80c55d635 100644 --- a/src/components/palette_editor/palette_editor.vue +++ b/src/components/palette_editor/palette_editor.vue @@ -16,7 +16,7 @@ @update:model-value="value => updatePalette(key, value)" /> @@ -136,11 +136,15 @@ const mobile = computed(() => { }) const contrast = computed(() => { + if (props.modelValue == null) return null const bg = hex2rgb(props.modelValue.bg) + const text = hex2rgb(props.modelValue.text) + const link = hex2rgb(props.modelValue.link) + if (text == null || link == null) return null return { - text: hints(getContrastRatio(bg, hex2rgb(props.modelValue.text))), - link: hints(getContrastRatio(bg, hex2rgb(props.modelValue.link))), + text: hints(getContrastRatio(bg, text)), + link: hints(getContrastRatio(bg, link)), } }) From 01dd3876f01c4e3d86c45ad640e91efa7d8d8f4e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 6 Jul 2026 19:43:43 +0300 Subject: [PATCH 8/9] use local colors for code highlight --- src/App.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.scss b/src/App.scss index be3f0df22..0de5a3577 100644 --- a/src/App.scss +++ b/src/App.scss @@ -145,8 +145,8 @@ h4 { } code { - background: var(--bg); - border: 1px solid var(--fg); + background: var(--background); + border: 1px solid var(--border); border-radius: var(--roundness); padding: 0 0.2em; From 1f667832f80cd83e866396163a09678a01468429 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 6 Jul 2026 19:47:03 +0300 Subject: [PATCH 9/9] stylistic changes to palette editor + mobile fixes --- src/components/palette_editor/palette_editor.vue | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/components/palette_editor/palette_editor.vue b/src/components/palette_editor/palette_editor.vue index 80c55d635..b27b21f00 100644 --- a/src/components/palette_editor/palette_editor.vue +++ b/src/components/palette_editor/palette_editor.vue @@ -20,6 +20,7 @@ :show-ratio="true" :contrast="contrast[key]" /> +
{{ ' ' }}
@@ -238,15 +239,6 @@ const updatePalette = (paletteKey, value) => { grid-column: 1 / span 2; } } - - .color-input { - display: grid; - gap: 0.5em; - - label { - flex: 1; - } - } } }