From fafa399d74c39e6c932f6dc48841e6be8b061a17 Mon Sep 17 00:00:00 2001 From: notfire Date: Thu, 14 Aug 2025 20:19:36 -0400 Subject: [PATCH 1/7] add option to open conversation view by clicking empty space in posts --- README.md | 1 + src/components/settings_modal/tabs/general_tab.vue | 8 ++++++++ src/components/status/status.js | 9 +++++++++ src/components/status/status.vue | 1 + src/i18n/en.json | 1 + src/modules/config.js | 1 + src/modules/instance.js | 1 + 7 files changed, 22 insertions(+) diff --git a/README.md b/README.md index 2890bafa..0685d9ab 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ - add support for accepted follow request notifications - add option to prevent page from getting pushed when making a new post with streaming api enabled (similar to 3rd "for unreads" option) - add link on user card to open profiles in iceshrimp-fe +- add option to open conversation view by clicking empty space in posts - add a script to download ruffle for flash support (requires python3 and requests library) - download by entering `tools/` and running `python3 download_ruffle.py` diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index 8b7fa9e4..e7fea7fb 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -495,6 +495,14 @@ {{ $t('settings.no_rich_text_description') }} +
  • + + {{ $t('settings.click_empty_to_open_conversation') }} + +
  • {{ $t('settings.attachments') }}

  • Date: Fri, 15 Aug 2025 18:19:51 -0400 Subject: [PATCH 2/7] add ability to close reply box under posts that were deleted --- README.md | 2 ++ src/components/post_status_form/post_status_form.js | 3 ++- src/components/post_status_form/post_status_form.vue | 10 ++++++++++ src/components/settings_modal/tabs/general_tab.vue | 8 ++++++++ src/components/status/status.js | 2 ++ src/i18n/en.json | 2 ++ 6 files changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0685d9ab..0b02e0cb 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ - add option to prevent page from getting pushed when making a new post with streaming api enabled (similar to 3rd "for unreads" option) - add link on user card to open profiles in iceshrimp-fe - add option to open conversation view by clicking empty space in posts +- add ability to close the reply box under posts that were deleted + - also an option to close it automatically - add a script to download ruffle for flash support (requires python3 and requests library) - download by entering `tools/` and running `python3 download_ruffle.py` diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 25eac1d6..0617facc 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -243,7 +243,8 @@ const PostStatusForm = { activeEmojiInput: undefined, activeTextInput: undefined, subjectVisible: showSubject, - showingPostConfirmDialog: false + showingPostConfirmDialog: false, + notClosed: true } }, computed: { diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index ebab96ab..f6d38119 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -295,6 +295,16 @@
    +
    + +
  • +
  • + + {{ $t('settings.auto_close_reply_ui_when_post_deleted') }} + +
  • Date: Fri, 15 Aug 2025 18:20:56 -0400 Subject: [PATCH 3/7] remove unused variable --- src/components/post_status_form/post_status_form.js | 3 +-- 1 file changed, 1 insertion(+), 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 0617facc..25eac1d6 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -243,8 +243,7 @@ const PostStatusForm = { activeEmojiInput: undefined, activeTextInput: undefined, subjectVisible: showSubject, - showingPostConfirmDialog: false, - notClosed: true + showingPostConfirmDialog: false } }, computed: { From 75ed21410fbf896511beb30928a77d8e496b3f07 Mon Sep 17 00:00:00 2001 From: notfire Date: Fri, 15 Aug 2025 18:26:17 -0400 Subject: [PATCH 4/7] add more requirements for fafa399d --- src/components/status/status.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index bd7dfdeb..560d678b 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -536,9 +536,10 @@ const Status = { }, goToThread (id, e) { if (this.$store.getters.mergedConfig.clickEmptyToOpenConversation) { + console.log(e.target) let targetType = e.target.tagName let targetClasses = e.target.className - if ((targetType !== "DIV" && targetType !== "SPAN") || (targetType === "SPAN" && targetClasses.length === 0) || (targetClasses.includes("userName") || targetClasses.includes("form-bottom") || targetClasses.includes("visibility-tray"))) + if ((targetType !== "DIV" && targetType !== "SPAN") || (targetType === "SPAN" && targetClasses.length === 0) || (targetClasses.includes("userName") || targetClasses.includes("form-bottom") || targetClasses.includes("visibility-tray") || targetClasses.includes("reply-form") || targetClasses.includes("post-status-form") || targetClasses.includes("form-group") || targetClasses.includes("preview-status"))) return this.$router.push({ name: 'conversation', params: { id: id } }) } From 5941619a2a2f0785c720248a5bb6602536236fb2 Mon Sep 17 00:00:00 2001 From: notfire Date: Fri, 15 Aug 2025 18:27:06 -0400 Subject: [PATCH 5/7] remove unnecessary console.log --- src/components/status/status.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index 560d678b..ab5d829d 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -536,7 +536,6 @@ const Status = { }, goToThread (id, e) { if (this.$store.getters.mergedConfig.clickEmptyToOpenConversation) { - console.log(e.target) let targetType = e.target.tagName let targetClasses = e.target.className if ((targetType !== "DIV" && targetType !== "SPAN") || (targetType === "SPAN" && targetClasses.length === 0) || (targetClasses.includes("userName") || targetClasses.includes("form-bottom") || targetClasses.includes("visibility-tray") || targetClasses.includes("reply-form") || targetClasses.includes("post-status-form") || targetClasses.includes("form-group") || targetClasses.includes("preview-status"))) From 0b05ec0ce16d29ed931103a7cd2476b528dd2f4e Mon Sep 17 00:00:00 2001 From: notfire Date: Sun, 7 Sep 2025 15:02:04 -0400 Subject: [PATCH 6/7] add support for misskey-like cat fields (is cat & speak as cat) and frontend options for this --- README.md | 12 ++ package.json | 1 + src/boot/after_store.js | 1 + .../settings_modal/tabs/general_tab.js | 3 + .../settings_modal/tabs/general_tab.vue | 29 ++++ src/components/status_body/status_body.js | 13 ++ src/components/status_body/status_body.vue | 2 +- src/components/user_avatar/user_avatar.js | 25 +++- src/components/user_avatar/user_avatar.vue | 132 ++++++++++++++++++ src/components/user_card/user_card.vue | 8 +- src/i18n/en.json | 5 + src/modules/config.js | 4 + src/modules/instance.js | 4 + .../entity_normalizer.service.js | 2 + yarn.lock | 5 + 15 files changed, 243 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0b02e0cb..bcac27c9 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,15 @@ - emojis are sourced from discord's list of emojis - emojis are now sorted by category (like on every other emoji picker) instead of alphabetically - script to get new emojis is included for future unicode versions +- cat stuff!! + - toggles for: + - showing "cat" badge on user profiles + - showing cat ears behind users' avatars + - "nyaize"-ing posts from authors with "Speak as Cat" enabled + - this requires [a backend patch](https://git.notfire.cc/notfire/iceshrimp-patches/src/branch/main/0007-cat-fields-in-akko-api.patch) - add toggle for showing post edit notifications - add support for accepted follow request notifications + - this requires [a backend patch](https://git.notfire.cc/notfire/iceshrimp-patches/src/branch/main/0005-previews-in-mastoapi.patch) - add option to prevent page from getting pushed when making a new post with streaming api enabled (similar to 3rd "for unreads" option) - add link on user card to open profiles in iceshrimp-fe - add option to open conversation view by clicking empty space in posts @@ -42,6 +49,11 @@ - add a script to download ruffle for flash support (requires python3 and requests library) - download by entering `tools/` and running `python3 download_ruffle.py` +### also of note for anyone running this fork +- aside from the stuff mentioned above, the following features will require patches to work: + - mfm ([patch](https://git.notfire.cc/notfire/iceshrimp-patches/src/branch/main/0004-mfm-in-mastoapi.patch)) + - previewing posts ([patch](https://git.notfire.cc/notfire/iceshrimp-patches/src/branch/main/0005-previews-in-mastoapi.patch)) + --- # Akkoma-FE diff --git a/package.json b/package.json index 5f8574dd..f087fb51 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "cropperjs": "^1.6.2", "diff": "^5.2.0", "escape-html": "^1.0.3", + "fast-average-color": "^9.5.0", "iso-639-1": "^2.1.15", "js-cookie": "^3.0.1", "localforage": "^1.10.0", diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 90bcdab3..7af0458d 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -180,6 +180,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => { copyInstanceOption('alwaysShowSubjectInput') copyInstanceOption('showFeaturesPanel') copyInstanceOption('hideSitename') + copyInstanceOption('showCatFields') copyInstanceOption('renderMisskeyMarkdown') copyInstanceOption('sidebarRight') diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index a829b0bf..3ce90bd1 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -135,6 +135,9 @@ const GeneralTab = { this.$store.dispatch('setOption', { name: 'postLanguage', value: val }) } }, + catOptsAvail: { + get: function () { return this.$store.getters.mergedConfig.showCatFields } + }, ...SharedComputedObject() }, methods: { diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue index dc64d348..b6c7e626 100644 --- a/src/components/settings_modal/tabs/general_tab.vue +++ b/src/components/settings_modal/tabs/general_tab.vue @@ -319,6 +319,35 @@ {{ $t('settings.search_pagination_limit') }}
  • +
  • +

    {{ $t('settings.cat_settings') }}

    +
  • +
  • + + {{ $t('settings.show_is_cat_on_user_card') }} + +
  • +
  • + + {{ $t('settings.show_cat_ears') }} + +
  • +
  • + + {{ $t('settings.nyaize_posts') }} + +
  • {{ $t('settings.columns') }}

  • diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js index 7ff6c3aa..2a1cbaed 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -86,6 +86,9 @@ const StatusContent = { translationLanguages () { return (this.$store.state.instance.supportedTranslationLanguages.source || []).map(lang => ({ key: lang.code, value: lang.code, label: lang.name })) }, + canSpeakAsCat () { + return (this.$store.getters.mergedConfig.nyaizePosts && this.status.user.speak_as_cat) + }, ...mapGetters(['mergedConfig']) }, components: { @@ -140,6 +143,16 @@ const StatusContent = { this.$store.dispatch( 'translateStatus', { id: this.status.id, language: translateTo, from: this.translateFrom } ).finally(() => { this.translating = false }) + }, + speakAsCat () { + // taken from https://github.com/misskey-dev/misskey/blob/develop/packages/misskey-js/src/nyaize.ts + const enRegex1 = /(?<=n)a/gi + const enRegex2 = /(?<=morn)ing/gi + const enRegex3 = /(?<=every)one/gi + return this.status.text + .replace(enRegex1, x => x === 'A' ? 'YA' : 'ya') + .replace(enRegex2, x => x === 'ING' ? 'YAN' : 'yan') + .replace(enRegex3, x => x === 'ONE' ? 'NYAN' : 'nyan') } } } diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue index 068c6c1b..1bc48909 100644 --- a/src/components/status_body/status_body.vue +++ b/src/components/status_body/status_body.vue @@ -48,7 +48,7 @@ { + this.earsColor = fac.getColor(img).hex; + this.earsReady = true; + } + } + } + } } } diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue index e64b54df..4bddd55b 100644 --- a/src/components/user_avatar/user_avatar.vue +++ b/src/components/user_avatar/user_avatar.vue @@ -3,6 +3,14 @@ class="Avatar" :class="{ '-compact': compact }" > +
    +
    +
    +
    + @import '../../_variables.scss'; +/* ears ripped from https://github.com/ihateblueb/aster/blob/main/packages/frontend/src/lib/components/Avatar.svelte */ .Avatar { --_avatarShadowBox: var(--avatarStatusShadow); --_avatarShadowFilter: var(--avatarStatusShadowFilter); @@ -40,6 +50,17 @@ width: 48px; height: 48px; + &:hover { + .ears { + .earLeft { + animation: earwiggleleft 1s infinite; + } + .earRight { + animation: earwiggleright 1s infinite; + } + } + } + &.-compact { width: 32px; height: 32px; @@ -90,5 +111,116 @@ border-radius: var(--tooltipRadius); } + .ears { + contain: strict; + position: absolute; + z-index: 0; + display: flex; + + top: -50%; + left: -50%; + width: 100%; + height: 100%; + padding: 50%; + + pointer-events: none; + + .earLeft, + .earRight { + contain: strict; + display: inline-block; + height: 50%; + width: 50%; + background: currentColor; + + &::after { + contain: strict; + content: ''; + display: block; + width: 60%; + height: 60%; + margin: 20%; + background: #df548f; + } + } + + .earLeft { + transform: rotate(37.5deg) skew(30deg); + + &, + &::after { + border-radius: 25% 75% 75%; + } + } + + .earRight { + transform: rotate(-37.5deg) skew(-30deg); + + &, + &::after { + border-radius: 75% 25% 75% 75%; + } + } + } + + @keyframes earwiggleleft { + from { + transform: rotate(37.6deg) skew(30deg); + } + 25% { + transform: rotate(10deg) skew(30deg); + } + 50% { + transform: rotate(20deg) skew(30deg); + } + 75% { + transform: rotate(0deg) skew(30deg); + } + to { + transform: rotate(37.6deg) skew(30deg); + } + } + + @keyframes earwiggleright { + from { + transform: rotate(-37.6deg) skew(-30deg); + } + 30% { + transform: rotate(-10deg) skew(-30deg); + } + 55% { + transform: rotate(-20deg) skew(-30deg); + } + 75% { + transform: rotate(0deg) skew(-30deg); + } + to { + transform: rotate(-37.6deg) skew(-30deg); + } + } + + @keyframes eartightleft { + from { + transform: rotate(37.6deg) skew(30deg); + } + 50% { + transform: rotate(37.4deg) skew(30deg); + } + to { + transform: rotate(37.6deg) skew(30deg); + } + } + + @keyframes eartightright { + from { + transform: rotate(-37.6deg) skew(-30deg); + } + 50% { + transform: rotate(-37.4deg) skew(-30deg); + } + to { + transform: rotate(-37.6deg) skew(-30deg); + } + } } diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue index dc876522..cfc9eac6 100644 --- a/src/components/user_card/user_card.vue +++ b/src/components/user_card/user_card.vue @@ -54,7 +54,7 @@ > @{{ user.screen_name_ui }} - + {{ $t('user_card.bot') }} + + {{ $t('user_card.is_cat') }} + { output.friends_count = data.following_count output.bot = data.bot + output.is_cat = typeof data.is_cat !== 'undefined' ? data.is_cat : false + output.speak_as_cat = typeof data.speak_as_cat !== 'undefined' ? data.speak_as_cat : false output.accepts_direct_messages_from = data.accepts_direct_messages_from output.follow_requests_count = data.follow_requests_count if (data.akkoma) { diff --git a/yarn.lock b/yarn.lock index 6abf579f..17ef3fbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3998,6 +3998,11 @@ extract-zip@^2.0.1: optionalDependencies: "@types/yauzl" "^2.9.1" +fast-average-color@^9.5.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/fast-average-color/-/fast-average-color-9.5.0.tgz" + integrity sha512-nC6x2YIlJ9xxgkMFMd1BNoM1ctMjNoRKfRliPmiEWW3S6rLTHiQcy9g3pt/xiKv/D0NAAkhb9VyV+WJFvTqMGg== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" From 4f402d43a47966d191c186bf46f7e1a8a9099963 Mon Sep 17 00:00:00 2001 From: notfire Date: Sun, 7 Sep 2025 19:17:17 -0400 Subject: [PATCH 7/7] hopefully fix 429s and the setDeleted error --- src/components/still-image/still-image.js | 25 +++++++++++++++-------- src/modules/statuses.js | 6 ++++-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js index a4c24df7..2765178a 100644 --- a/src/components/still-image/still-image.js +++ b/src/components/still-image/still-image.js @@ -58,6 +58,13 @@ const StillImage = { } }, detectAnimationWithFetch (image) { + if (!this.$store.fetchedimgs) + this.$store.fetchedimgs = [] + else + if (this.$store.fetchedimgs.indexOf(image.src) === -1) + this.$store.fetchedimgs.push(image.src) + else + return // Browser Cache should ensure image doesn't get loaded twice if cache exists fetch(image.src, { referrerPolicy: 'same-origin' @@ -110,7 +117,7 @@ const StillImage = { // this.setLabel('PNG') // return // } - + // Hail mary for extensionless if (extension.includes('/')) { // Don't mind the CORS error barrage @@ -170,14 +177,14 @@ const StillImage = { drawThumbnail() { const canvas = this.$refs.canvas; if (!canvas) return; - + const context = canvas.getContext('2d'); const image = this.$refs.src; const parentElement = canvas.parentElement; - + // 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 @@ -191,13 +198,13 @@ const StillImage = { 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; @@ -205,7 +212,7 @@ const StillImage = { 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'; @@ -215,7 +222,7 @@ const StillImage = { const dy = (parentElement.clientHeight - drawHeight) / 2; context.drawImage(image, dx, dy, drawWidth, drawHeight); }); - } + } }, updated () { // On computed animated change diff --git a/src/modules/statuses.js b/src/modules/statuses.js index bb1eac2f..2d017a93 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -467,8 +467,10 @@ export const mutations = { newStatus.bookmarked = status.bookmarked }, setDeleted (state, { status }) { - const newStatus = state.allStatusesObject[status.id] - if (newStatus) newStatus.deleted = true + if (status) { + const newStatus = state.allStatusesObject[status.id] + if (newStatus) newStatus.deleted = true + } }, setManyDeleted (state, condition) { Object.values(state.allStatusesObject).forEach(status => {