From 14a9e1050ba57bcb4cf82b48829accc98c9bd504 Mon Sep 17 00:00:00 2001 From: Kaity A <supakaity@blahaj.zone> Date: Tue, 24 Oct 2023 21:16:09 +1000 Subject: [PATCH 01/17] feat: allow using wildcards in antenna --- locales/en-US.yml | 2 +- locales/es-ES.yml | 2 +- packages/backend/src/core/AntennaService.ts | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index ddcf610903..e573a15b09 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -402,7 +402,7 @@ antennaKeywordsDescription: "Separate with spaces for an AND condition or with l notifyAntenna: "Notify about new notes" withFileAntenna: "Only notes with files" enableServiceworker: "Enable Push-Notifications for your Browser" -antennaUsersDescription: "List one username per line" +antennaUsersDescription: "List one username per line. Use \"*@instance.com\" to specify all users of an instance" caseSensitive: "Case sensitive" withReplies: "Include replies" connectedTo: "Following account(s) are connected" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 82a996efbb..f1193d567a 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -391,7 +391,7 @@ antennaKeywordsDescription: "Separar con espacios es una declaración AND, separ notifyAntenna: "Notificar nueva nota" withFileAntenna: "Sólo notas con archivos adjuntados" enableServiceworker: "Activar ServiceWorker" -antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva" +antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva. Utilice \"*@instance.com\" para especificar todos los usuarios de una instancia." caseSensitive: "Distinguir mayúsculas de minúsculas" withReplies: "Incluir respuestas" connectedTo: "Estas cuentas están conectadas" diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts index 65be275548..f5db80dedd 100644 --- a/packages/backend/src/core/AntennaService.ts +++ b/packages/backend/src/core/AntennaService.ts @@ -115,13 +115,17 @@ export class AntennaService implements OnApplicationShutdown { const { username, host } = Acct.parse(x); return this.utilityService.getFullApAccount(username, host).toLowerCase(); }); - if (!accts.includes(this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false; + const matchUser = this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase(); + const matchWildcard = this.utilityService.getFullApAccount('*', noteUser.host).toLowerCase(); + if (!accts.includes(matchUser) && !accts.includes(matchWildcard)) return false; } else if (antenna.src === 'users_blacklist') { const accts = antenna.users.map(x => { const { username, host } = Acct.parse(x); return this.utilityService.getFullApAccount(username, host).toLowerCase(); }); - if (accts.includes(this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase())) return false; + const matchUser = this.utilityService.getFullApAccount(noteUser.username, noteUser.host).toLowerCase(); + const matchWildcard = this.utilityService.getFullApAccount('*', noteUser.host).toLowerCase(); + if (accts.includes(matchUser) || accts.includes(matchWildcard)) return false; } const keywords = antenna.keywords From 25e6409cc91fc7ad733f8c8154aca3d55124c5c1 Mon Sep 17 00:00:00 2001 From: dakkar <dakkar@thenautilus.net> Date: Wed, 20 Mar 2024 15:38:20 +0000 Subject: [PATCH 02/17] allow overriding all string config values via env - fixes #465 will need end-user documentation! --- packages/backend/src/config.ts | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index c99bc7ae03..1e08a0f6a8 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -212,6 +212,8 @@ export function loadConfig(): Config { {} as Source, ) as Source; + applyEnvOverrides(config); + const url = tryCreateUrl(config.url); const version = meta.version; const host = url.host; @@ -304,3 +306,102 @@ function convertRedisOptions(options: RedisOptionsSource, host: string): RedisOp db: options.db ?? 0, }; } + +/* + this function allows overriding any string-valued config option with + a sensible-named environment variable + + e.g. `MK_CONFIG_MEILISEARCH_APIKEY` overrides `config.meilisearch.apikey` + + the option's containing object must be present in the config *file*, + so in the example above, `config.meilisearch` must be set to + something in the file, it can't be completely commented out. + + you can also override a single `dbSlave` value, + e.g. `MK_CONFIG_DBSLAVES_1_PASS` sets the password for the 2nd + database replica (the first one would be + `MK_CONFIG_DBSLAVES_0_PASS`); again, `config.dbSlaves` must be set + to an array of the right size already in the file + + values can be read from files, too: setting `MK_DB_PASS_FILE` to + `/some/file` would set the main database password to the contents of + `/some/file` (trimmed of whitespaces) + */ +function applyEnvOverrides(config: Source) { + // these inner functions recurse through the config structure, using + // the given steps, building the env variable name + + function _apply_top(steps: (string | number)[]) { + _apply_inner(config, '', steps); + } + + function _apply_inner(thisConfig: any, name: string, steps: (string | number)[]) { + // are there more steps after this one? recurse + if (steps.length > 1) { + const thisStep = steps.shift(); + if (thisStep === null || thisStep === undefined) return; + + // if a step is not a simple value, iterate through it + if (typeof thisStep === 'object') { + for (const thisOneStep of thisStep) { + _descend(thisConfig, name, thisOneStep, steps); + } + } else { + _descend(thisConfig, name, thisStep, steps); + } + + // the actual override has happened at the bottom of the + // recursion, we're done + return; + } + + // this is the last step, same thing as above + const lastStep = steps[0]; + + if (typeof lastStep === 'object') { + for (const lastOneStep of lastStep) { + _lastBit(thisConfig, name, lastOneStep); + } + } else { + _lastBit(thisConfig, name, lastStep); + } + } + + // this recurses down, bailing out if there's no config to override + function _descend(thisConfig: any, name: string, thisStep: string | number, steps: (string | number)[]) { + name = `${name}${thisStep.toString().toUpperCase()}_`; + thisConfig = thisConfig[thisStep]; + if (!thisConfig) return; + _apply_inner(thisConfig, name, steps); + } + + // this is the bottom of the recursion: look at the environment and + // set the value + function _lastBit(thisConfig: any, name: string, lastStep: string | number) { + name = `${name}${lastStep.toString().toUpperCase()}`; + + const val = process.env[`MK_CONFIG_${name}`]; + if (val != null && val != undefined) { + thisConfig[lastStep] = val; + } + + const file = process.env[`MK_CONFIG_${name}_FILE`]; + if (file) { + thisConfig[lastStep] = fs.readFileSync(file, 'utf-8').trim(); + } + } + + // these are all the settings that can be overridden + + _apply_top([['url', 'port', 'socket', 'chmodSocket', 'disableHsts']]); + _apply_top(['db', ['host', 'port', 'db', 'user', 'pass']]); + _apply_top(['dbSlaves', config.dbSlaves?.keys(), ['host', 'port', 'db', 'user', 'pass']]); + _apply_top([ + ['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines'], + ['host','port','username','pass','db','prefix'], + ]); + _apply_top(['meilisearch', ['host', 'port', 'apikey', 'ssl', 'index', 'scope']]); + _apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]); + _apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'videoThumbnailGenerator']]); + _apply_top([['maxFileSize', 'maxNoteLength', 'pidFile']]); +} From 435cab01c8cf7f29e873eb7d9711dd0ad2ead816 Mon Sep 17 00:00:00 2001 From: dakkar <dakkar@thenautilus.net> Date: Thu, 21 Mar 2024 10:00:16 +0000 Subject: [PATCH 03/17] deal with (possible, future) non-alnum config keys --- packages/backend/src/config.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 1e08a0f6a8..8f814e4520 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -367,9 +367,13 @@ function applyEnvOverrides(config: Source) { } } + function _step2name(step: string|number): string { + return step.toString().replaceAll(/[^a-z0-9]+/gi,'').toUpperCase(); + } + // this recurses down, bailing out if there's no config to override function _descend(thisConfig: any, name: string, thisStep: string | number, steps: (string | number)[]) { - name = `${name}${thisStep.toString().toUpperCase()}_`; + name = `${name}${_step2name(thisStep)}_`; thisConfig = thisConfig[thisStep]; if (!thisConfig) return; _apply_inner(thisConfig, name, steps); @@ -378,7 +382,7 @@ function applyEnvOverrides(config: Source) { // this is the bottom of the recursion: look at the environment and // set the value function _lastBit(thisConfig: any, name: string, lastStep: string | number) { - name = `${name}${lastStep.toString().toUpperCase()}`; + name = `${name}${_step2name(lastStep)}`; const val = process.env[`MK_CONFIG_${name}`]; if (val != null && val != undefined) { From 74362af828b78d6e8b16cde00996db2afb63a4fe Mon Sep 17 00:00:00 2001 From: dakkar <dakkar@thenautilus.net> Date: Sat, 23 Mar 2024 12:19:13 +0000 Subject: [PATCH 04/17] allow custom oneko image via themes - fixes #472 after this change, one can set a custom image by: * upload an appropriate image to the drive (some images can be found at https://github.com/vencordcss/onekocord/tree/main/onekoskins), possibly with the "keep original" option set * copy the URL to the image in the drive * create/edit a theme so that it contains (inside `props`): "oneko-image": '"url(https://yourinstance.example.com/files/ee17b385-a084-4e2a-b531-225dfb96cc3c)', with the proper URL That's it! --- packages/frontend/src/components/SkOneko.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/SkOneko.vue b/packages/frontend/src/components/SkOneko.vue index fbf50067a9..a82258e97e 100644 --- a/packages/frontend/src/components/SkOneko.vue +++ b/packages/frontend/src/components/SkOneko.vue @@ -235,6 +235,6 @@ onMounted(init); pointer-events: none; image-rendering: pixelated; z-index: 2147483647; - background-image: url(/client-assets/oneko.gif); + background-image: var(--oneko-image, url(/client-assets/oneko.gif)); } </style> From 0e8cdb30b719a1cc0d9fc3e9c14eacb54444caeb Mon Sep 17 00:00:00 2001 From: dakkar <dakkar@thenautilus.net> Date: Sun, 24 Mar 2024 11:12:17 +0000 Subject: [PATCH 05/17] allow setting values not present in the config file replicas and arrays in general, are more complicated :/ --- packages/backend/src/config.ts | 53 +++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 8f814e4520..8d4c5464a6 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -311,17 +311,13 @@ function convertRedisOptions(options: RedisOptionsSource, host: string): RedisOp this function allows overriding any string-valued config option with a sensible-named environment variable - e.g. `MK_CONFIG_MEILISEARCH_APIKEY` overrides `config.meilisearch.apikey` - - the option's containing object must be present in the config *file*, - so in the example above, `config.meilisearch` must be set to - something in the file, it can't be completely commented out. + e.g. `MK_CONFIG_MEILISEARCH_APIKEY` sets `config.meilisearch.apikey` you can also override a single `dbSlave` value, e.g. `MK_CONFIG_DBSLAVES_1_PASS` sets the password for the 2nd database replica (the first one would be - `MK_CONFIG_DBSLAVES_0_PASS`); again, `config.dbSlaves` must be set - to an array of the right size already in the file + `MK_CONFIG_DBSLAVES_0_PASS`); in this case, `config.dbSlaves` must + be set to an array of the right size already in the file values can be read from files, too: setting `MK_DB_PASS_FILE` to `/some/file` would set the main database password to the contents of @@ -332,10 +328,10 @@ function applyEnvOverrides(config: Source) { // the given steps, building the env variable name function _apply_top(steps: (string | number)[]) { - _apply_inner(config, '', steps); + _walk('', [], steps); } - function _apply_inner(thisConfig: any, name: string, steps: (string | number)[]) { + function _walk(name: string, path: (string | number)[], steps: (string | number)[]) { // are there more steps after this one? recurse if (steps.length > 1) { const thisStep = steps.shift(); @@ -344,10 +340,10 @@ function applyEnvOverrides(config: Source) { // if a step is not a simple value, iterate through it if (typeof thisStep === 'object') { for (const thisOneStep of thisStep) { - _descend(thisConfig, name, thisOneStep, steps); + _descend(name, path, thisOneStep, steps); } } else { - _descend(thisConfig, name, thisStep, steps); + _descend(name, path, thisStep, steps); } // the actual override has happened at the bottom of the @@ -360,10 +356,10 @@ function applyEnvOverrides(config: Source) { if (typeof lastStep === 'object') { for (const lastOneStep of lastStep) { - _lastBit(thisConfig, name, lastOneStep); + _lastBit(name, path, lastOneStep); } } else { - _lastBit(thisConfig, name, lastStep); + _lastBit(name, path, lastStep); } } @@ -372,29 +368,40 @@ function applyEnvOverrides(config: Source) { } // this recurses down, bailing out if there's no config to override - function _descend(thisConfig: any, name: string, thisStep: string | number, steps: (string | number)[]) { + function _descend(name: string, path: (string | number)[], thisStep: string | number, steps: (string | number)[]) { name = `${name}${_step2name(thisStep)}_`; - thisConfig = thisConfig[thisStep]; - if (!thisConfig) return; - _apply_inner(thisConfig, name, steps); + path = [ ...path, thisStep ]; + _walk(name, path, steps); } // this is the bottom of the recursion: look at the environment and // set the value - function _lastBit(thisConfig: any, name: string, lastStep: string | number) { - name = `${name}${_step2name(lastStep)}`; + function _lastBit(name: string, path: (string | number)[], lastStep: string | number) { + name = `MK_CONFIG_${name}${_step2name(lastStep)}`; - const val = process.env[`MK_CONFIG_${name}`]; + const val = process.env[name]; if (val != null && val != undefined) { - thisConfig[lastStep] = val; + _assign(path, lastStep, val); } - const file = process.env[`MK_CONFIG_${name}_FILE`]; + const file = process.env[`${name}_FILE`]; if (file) { - thisConfig[lastStep] = fs.readFileSync(file, 'utf-8').trim(); + _assign(path, lastStep, fs.readFileSync(file, 'utf-8').trim()); } } + function _assign(path: (string | number)[], lastStep: string | number, value: string) { + let thisConfig = config; + for (const step of path) { + if (!thisConfig[step]) { + thisConfig[step] = {}; + } + thisConfig = thisConfig[step]; + } + + thisConfig[lastStep] = value; + } + // these are all the settings that can be overridden _apply_top([['url', 'port', 'socket', 'chmodSocket', 'disableHsts']]); From 4271402e0d0e1840791158de288a3e7617227ec4 Mon Sep 17 00:00:00 2001 From: dakkar <dakkar@thenautilus.net> Date: Sun, 24 Mar 2024 11:17:55 +0000 Subject: [PATCH 06/17] recognise numbers and boolean values --- packages/backend/src/config.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 8d4c5464a6..f6ce9b3cdf 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -390,6 +390,8 @@ function applyEnvOverrides(config: Source) { } } + const alwaysStrings = { 'chmodSocket': 1 }; + function _assign(path: (string | number)[], lastStep: string | number, value: string) { let thisConfig = config; for (const step of path) { @@ -399,6 +401,14 @@ function applyEnvOverrides(config: Source) { thisConfig = thisConfig[step]; } + if (!alwaysStrings[lastStep]) { + if (value.match(/^[0-9]+$/)) { + value = parseInt(value); + } else if (value.match(/^(true|false)$/i)) { + value = !!value.match(/^true$/i); + } + } + thisConfig[lastStep] = value; } From 194d8a5527ed98a2f9679e1381a6d7290f10a1b7 Mon Sep 17 00:00:00 2001 From: Sugar <sugar@sylveon.social> Date: Sat, 11 May 2024 09:44:03 +0200 Subject: [PATCH 07/17] feat: send edit events to servers that interacted a server replied to, renoted or reacted to a note knows about a note, and as such it should get notified about it being edited. this matches similar logic in mastodon. --- packages/backend/src/core/NoteEditService.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index a01dfec664..399461dd70 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -699,6 +699,24 @@ export class NoteEditService implements OnApplicationShutdown { dm.addFollowersRecipe(); } + if (['public', 'home'].includes(note.visibility)) { + // Send edit event to all users who replied to, + // renoted a post or reacted to a note. + const noteId = note.id; + const users = await this.usersRepository.createQueryBuilder() + .where( + 'id IN (SELECT "userId" FROM note WHERE "replyId" = :noteId OR "renoteId" = :noteId UNION SELECT "userId" FROM note_reaction WHERE "noteId" = :noteId)', + { noteId }, + ) + .andWhere('host IS NOT NULL') + .getMany(); + for (const u of users) { + // User was verified to be remote by checking + // whether host IS NOT NULL in SQL query. + dm.addDirectRecipe(u as MiRemoteUser); + } + } + if (['public'].includes(note.visibility)) { this.relayService.deliverToRelays(user, noteActivity); } From eac9f389420b5ae3c18dfe0bf0c5aead8f164655 Mon Sep 17 00:00:00 2001 From: Sebastian Di Luzio <ap-soft-gitlab@diluz.io> Date: Mon, 20 May 2024 11:10:45 +0000 Subject: [PATCH 08/17] fix(i18n): adjust grammar in about_misskey --- locales/en-US.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index fe27a131ba..0203d9562d 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1830,7 +1830,7 @@ _registry: domain: "Domain" createKey: "Create key" _aboutMisskey: - about: "Sharkey is open-source software based on Misskey which has been in developed since 2014 by syuilo." + about: "Sharkey is open-source software based on Misskey which has been in development by syuilo since 2014." contributors: "Main contributors" allContributors: "All contributors" source: "Source code" From 4c4b43124862a4ee564cb8d6dafa44be8c95cb78 Mon Sep 17 00:00:00 2001 From: Leah <kevinlukej@gmail.com> Date: Thu, 23 May 2024 18:08:31 +0000 Subject: [PATCH 09/17] Ported cutiekeys followmouse mfm --- locales/en-US.yml | 3 + .../frontend/src/components/CkFollowMouse.vue | 81 +++++++++++ .../frontend/src/components/MkMfmWindow.vue | 134 ++++++++++-------- .../global/MkMisskeyFlavoredMarkdown.ts | 23 +++ packages/frontend/src/const.ts | 5 +- 5 files changed, 184 insertions(+), 62 deletions(-) create mode 100644 packages/frontend/src/components/CkFollowMouse.vue diff --git a/locales/en-US.yml b/locales/en-US.yml index fe27a131ba..07173c131e 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -2478,6 +2478,7 @@ _moderationLogTypes: unsetUserAvatar: "Unset this user's avatar" unsetUserBanner: "Unset this user's banner" _mfm: + uncommonFeature: "This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks" intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax." dummy: "Sharkey expands the world of the Fediverse" mention: "Mention" @@ -2542,6 +2543,8 @@ _mfm: rotateDescription: "Turns content by a specified angle." position: "Position" positionDescription: "Move content by a specified amount." + followMouse: "Follow Mouse" + followMouseDescription: "Content will follow the mouse. On mobile it will follow wherever the user taps." scale: "Scale" scaleDescription: "Scale content by a specified amount." foreground: "Foreground color" diff --git a/packages/frontend/src/components/CkFollowMouse.vue b/packages/frontend/src/components/CkFollowMouse.vue new file mode 100644 index 0000000000..b55a577b3f --- /dev/null +++ b/packages/frontend/src/components/CkFollowMouse.vue @@ -0,0 +1,81 @@ +<template> +<span ref="container" :class="$style.root"> + <span ref="el" :class="$style.inner" style="position: absolute"> + <slot></slot> + </span> +</span> +</template> + +<script lang="ts" setup> +import { onMounted, onUnmounted, shallowRef } from 'vue'; +const el = shallowRef<HTMLElement>(); +const container = shallowRef<HTMLElement>(); +const props = defineProps({ + x: { + type: Boolean, + default: true, + }, + y: { + type: Boolean, + default: true, + }, + speed: { + type: String, + default: '0.1s', + }, + rotateByVelocity: { + type: Boolean, + default: true, + }, +}); + +let lastX = 0; +let lastY = 0; +let oldAngle = 0; + +function lerp(a, b, alpha) { + return a + alpha * (b - a); +} + +const updatePosition = (mouseEvent: MouseEvent) => { + if (el.value && container.value) { + const containerRect = container.value.getBoundingClientRect(); + const newX = mouseEvent.clientX - containerRect.left; + const newY = mouseEvent.clientY - containerRect.top; + let transform = `translate(calc(${props.x ? newX : 0}px - 50%), calc(${props.y ? newY : 0}px - 50%))`; + if (props.rotateByVelocity) { + const deltaX = newX - lastX; + const deltaY = newY - lastY; + const angle = lerp( + oldAngle, + Math.atan2(deltaY, deltaX) * (180 / Math.PI), + 0.1, + ); + transform += ` rotate(${angle}deg)`; + oldAngle = angle; + } + el.value.style.transform = transform; + el.value.style.transition = `transform ${props.speed}`; + lastX = newX; + lastY = newY; + } +}; + +onMounted(() => { + window.addEventListener('mousemove', updatePosition); +}); + +onUnmounted(() => { + window.removeEventListener('mousemove', updatePosition); +}); +</script> + +<style lang="scss" module> +.root { + position: relative; + display: inline-block; +} +.inner { + transform-origin: center center; +} +</style> diff --git a/packages/frontend/src/components/MkMfmWindow.vue b/packages/frontend/src/components/MkMfmWindow.vue index ce2a0e7391..fd23eb0097 100644 --- a/packages/frontend/src/components/MkMfmWindow.vue +++ b/packages/frontend/src/components/MkMfmWindow.vue @@ -9,17 +9,17 @@ <template #header> MFM Cheatsheet </template> - <MkStickyContainer> + <MkStickyContainer> <MkSpacer :contentMax="800"> <div class="mfm-cheat-sheet"> <div>{{ i18n.ts._mfm.intro }}</div> - <br /> + <br/> <div class="section _block"> <div class="title">{{ i18n.ts._mfm.mention }}</div> <div class="content"> <p>{{ i18n.ts._mfm.mentionDescription }}</p> <div class="preview"> - <Mfm :text="preview_mention" /> + <Mfm :text="preview_mention"/> <MkTextarea v-model="preview_mention"><template #label>MFM</template></MkTextarea> </div> </div> @@ -29,7 +29,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.hashtagDescription }}</p> <div class="preview"> - <Mfm :text="preview_hashtag" /> + <Mfm :text="preview_hashtag"/> <MkTextarea v-model="preview_hashtag"><template #label>MFM</template></MkTextarea> </div> </div> @@ -39,7 +39,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.linkDescription }}</p> <div class="preview"> - <Mfm :text="preview_link" /> + <Mfm :text="preview_link"/> <MkTextarea v-model="preview_link"><template #label>MFM</template></MkTextarea> </div> </div> @@ -49,7 +49,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.emojiDescription }}</p> <div class="preview"> - <Mfm :text="preview_emoji" /> + <Mfm :text="preview_emoji"/> <MkTextarea v-model="preview_emoji"><template #label>MFM</template></MkTextarea> </div> </div> @@ -59,7 +59,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.boldDescription }}</p> <div class="preview"> - <Mfm :text="preview_bold" /> + <Mfm :text="preview_bold"/> <MkTextarea v-model="preview_bold"><template #label>MFM</template></MkTextarea> </div> </div> @@ -69,7 +69,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.smallDescription }}</p> <div class="preview"> - <Mfm :text="preview_small" /> + <Mfm :text="preview_small"/> <MkTextarea v-model="preview_small"><template #label>MFM</template></MkTextarea> </div> </div> @@ -79,7 +79,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.quoteDescription }}</p> <div class="preview"> - <Mfm :text="preview_quote" /> + <Mfm :text="preview_quote"/> <MkTextarea v-model="preview_quote"><template #label>MFM</template></MkTextarea> </div> </div> @@ -89,7 +89,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.centerDescription }}</p> <div class="preview"> - <Mfm :text="preview_center" /> + <Mfm :text="preview_center"/> <MkTextarea v-model="preview_center"><template #label>MFM</template></MkTextarea> </div> </div> @@ -99,7 +99,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.inlineCodeDescription }}</p> <div class="preview"> - <Mfm :text="preview_inlineCode" /> + <Mfm :text="preview_inlineCode"/> <MkTextarea v-model="preview_inlineCode"><template #label>MFM</template></MkTextarea> </div> </div> @@ -109,7 +109,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.blockCodeDescription }}</p> <div class="preview"> - <Mfm :text="preview_blockCode" /> + <Mfm :text="preview_blockCode"/> <MkTextarea v-model="preview_blockCode"><template #label>MFM</template></MkTextarea> </div> </div> @@ -119,7 +119,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.inlineMathDescription }}</p> <div class="preview"> - <Mfm :text="preview_inlineMath" /> + <Mfm :text="preview_inlineMath"/> <MkTextarea v-model="preview_inlineMath"><template #label>MFM</template></MkTextarea> </div> </div> @@ -129,7 +129,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.blockMathDescription }}</p> <div class="preview"> - <Mfm :text="preview_blockMath" /> + <Mfm :text="preview_blockMath"/> <MkTextarea v-model="preview_blockMath"><template #label>MFM</template></MkTextarea> </div> </div> @@ -139,7 +139,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.searchDescription }}</p> <div class="preview"> - <Mfm :text="preview_search" /> + <Mfm :text="preview_search"/> <MkTextarea v-model="preview_search"><template #label>MFM</template></MkTextarea> </div> </div> @@ -149,7 +149,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.flipDescription }}</p> <div class="preview"> - <Mfm :text="preview_flip" /> + <Mfm :text="preview_flip"/> <MkTextarea v-model="preview_flip"><template #label>MFM</template></MkTextarea> </div> </div> @@ -159,7 +159,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.fontDescription }}</p> <div class="preview"> - <Mfm :text="preview_font" /> + <Mfm :text="preview_font"/> <MkTextarea v-model="preview_font"><template #label>MFM</template></MkTextarea> </div> </div> @@ -169,7 +169,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.x2Description }}</p> <div class="preview"> - <Mfm :text="preview_x2" /> + <Mfm :text="preview_x2"/> <MkTextarea v-model="preview_x2"><template #label>MFM</template></MkTextarea> </div> </div> @@ -179,7 +179,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.x3Description }}</p> <div class="preview"> - <Mfm :text="preview_x3" /> + <Mfm :text="preview_x3"/> <MkTextarea v-model="preview_x3"><template #label>MFM</template></MkTextarea> </div> </div> @@ -189,7 +189,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.x4Description }}</p> <div class="preview"> - <Mfm :text="preview_x4" /> + <Mfm :text="preview_x4"/> <MkTextarea v-model="preview_x4"><template #label>MFM</template></MkTextarea> </div> </div> @@ -199,7 +199,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.blurDescription }}</p> <div class="preview"> - <Mfm :text="preview_blur" /> + <Mfm :text="preview_blur"/> <MkTextarea v-model="preview_blur"><template #label>MFM</template></MkTextarea> </div> </div> @@ -209,7 +209,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.jellyDescription }}</p> <div class="preview"> - <Mfm :text="preview_jelly" /> + <Mfm :text="preview_jelly"/> <MkTextarea v-model="preview_jelly"><template #label>MFM</template></MkTextarea> </div> </div> @@ -219,7 +219,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.tadaDescription }}</p> <div class="preview"> - <Mfm :text="preview_tada" /> + <Mfm :text="preview_tada"/> <MkTextarea v-model="preview_tada"><template #label>MFM</template></MkTextarea> </div> </div> @@ -229,7 +229,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.jumpDescription }}</p> <div class="preview"> - <Mfm :text="preview_jump" /> + <Mfm :text="preview_jump"/> <MkTextarea v-model="preview_jump"><template #label>MFM</template></MkTextarea> </div> </div> @@ -239,7 +239,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.bounceDescription }}</p> <div class="preview"> - <Mfm :text="preview_bounce" /> + <Mfm :text="preview_bounce"/> <MkTextarea v-model="preview_bounce"><template #label>MFM</template></MkTextarea> </div> </div> @@ -249,7 +249,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.spinDescription }}</p> <div class="preview"> - <Mfm :text="preview_spin" /> + <Mfm :text="preview_spin"/> <MkTextarea v-model="preview_spin"><template #label>MFM</template></MkTextarea> </div> </div> @@ -259,7 +259,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.shakeDescription }}</p> <div class="preview"> - <Mfm :text="preview_shake" /> + <Mfm :text="preview_shake"/> <MkTextarea v-model="preview_shake"><template #label>MFM</template></MkTextarea> </div> </div> @@ -269,7 +269,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.twitchDescription }}</p> <div class="preview"> - <Mfm :text="preview_twitch" /> + <Mfm :text="preview_twitch"/> <MkTextarea v-model="preview_twitch"><template #label>MFM</template></MkTextarea> </div> </div> @@ -279,7 +279,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.rainbowDescription }}</p> <div class="preview"> - <Mfm :text="preview_rainbow" /> + <Mfm :text="preview_rainbow"/> <MkTextarea v-model="preview_rainbow"><template #label>MFM</template></MkTextarea> </div> </div> @@ -289,7 +289,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.sparkleDescription }}</p> <div class="preview"> - <Mfm :text="preview_sparkle" /> + <Mfm :text="preview_sparkle"/> <MkTextarea v-model="preview_sparkle"><span>MFM</span></MkTextarea> </div> </div> @@ -299,7 +299,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.rotateDescription }}</p> <div class="preview"> - <Mfm :text="preview_rotate" /> + <Mfm :text="preview_rotate"/> <MkTextarea v-model="preview_rotate"><span>MFM</span></MkTextarea> </div> </div> @@ -309,17 +309,29 @@ <div class="content"> <p>{{ i18n.ts._mfm.positionDescription }}</p> <div class="preview"> - <Mfm :text="preview_position" /> + <Mfm :text="preview_position"/> <MkTextarea v-model="preview_position"><span>MFM</span></MkTextarea> </div> </div> </div> + <div class="section _block" style="overflow: hidden"> + <div class="title">{{ i18n.ts._mfm.followMouse }}</div> + <MkInfo warn>{{ i18n.ts._mfm.uncommonFeature }}</MkInfo> + <br/> + <div class="content"> + <p>{{ i18n.ts._mfm.followMouseDescription }}</p> + <div class="preview"> + <Mfm :text="preview_followmouse"/> + <MkTextarea v-model="preview_followmouse"><span>MFM</span></MkTextarea> + </div> + </div> + </div> <div class="section _block"> <div class="title">{{ i18n.ts._mfm.scale }}</div> <div class="content"> <p>{{ i18n.ts._mfm.scaleDescription }}</p> <div class="preview"> - <Mfm :text="preview_scale" /> + <Mfm :text="preview_scale"/> <MkTextarea v-model="preview_scale"><span>MFM</span></MkTextarea> </div> </div> @@ -329,7 +341,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.foregroundDescription }}</p> <div class="preview"> - <Mfm :text="preview_fg" /> + <Mfm :text="preview_fg"/> <MkTextarea v-model="preview_fg"><span>MFM</span></MkTextarea> </div> </div> @@ -339,7 +351,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.backgroundDescription }}</p> <div class="preview"> - <Mfm :text="preview_bg" /> + <Mfm :text="preview_bg"/> <MkTextarea v-model="preview_bg"><span>MFM</span></MkTextarea> </div> </div> @@ -349,7 +361,7 @@ <div class="content"> <p>{{ i18n.ts._mfm.plainDescription }}</p> <div class="preview"> - <Mfm :text="preview_plain" /> + <Mfm :text="preview_plain"/> <MkTextarea v-model="preview_plain"><span>MFM</span></MkTextarea> </div> </div> @@ -362,18 +374,19 @@ <script lang="ts" setup> import { ref } from 'vue'; +import MkInfo from './MkInfo.vue'; import MkWindow from '@/components/MkWindow.vue'; import MkTextarea from '@/components/MkTextarea.vue'; -import { i18n } from "@/i18n.js"; +import { i18n } from '@/i18n.js'; const emit = defineEmits<{ (ev: 'closed'): void; }>(); -const preview_mention = ref("@example"); -const preview_hashtag = ref("#test"); +const preview_mention = ref('@example'); +const preview_hashtag = ref('#test'); const preview_link = ref(`[${i18n.ts._mfm.dummy}](https://joinsharkey.org)`); -const preview_emoji = ref(`:heart:`); +const preview_emoji = ref(':heart:'); const preview_bold = ref(`**${i18n.ts._mfm.dummy}**`); const preview_small = ref( `<small>${i18n.ts._mfm.dummy}</small>`, @@ -386,33 +399,33 @@ const preview_blockCode = ref( '```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```', ); const preview_inlineMath = ref( - "\\(x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\)", + '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)', ); -const preview_blockMath = ref("\\[x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\]"); +const preview_blockMath = ref('\\[x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\]'); const preview_quote = ref(`> ${i18n.ts._mfm.dummy}`); const preview_search = ref( `${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [検索]`, ); const preview_jelly = ref( - "$[jelly 🍮] $[jelly.speed=3s 🍮] $[jelly.delay=3s 🍮] $[jelly.loop=3 🍮]", + '$[jelly 🍮] $[jelly.speed=3s 🍮] $[jelly.delay=3s 🍮] $[jelly.loop=3 🍮]', ); const preview_tada = ref( - "$[tada 🍮] $[tada.speed=3s 🍮] $[tada.delay=3s 🍮] $[tada.loop=3 🍮]", + '$[tada 🍮] $[tada.speed=3s 🍮] $[tada.delay=3s 🍮] $[tada.loop=3 🍮]', ); const preview_jump = ref( - "$[jump 🍮] $[jump.speed=3s 🍮] $[jump.delay=3s 🍮] $[jump.loop=3 🍮]", + '$[jump 🍮] $[jump.speed=3s 🍮] $[jump.delay=3s 🍮] $[jump.loop=3 🍮]', ); const preview_bounce = ref( - "$[bounce 🍮] $[bounce.speed=3s 🍮] $[bounce.delay=3s 🍮] $[bounce.loop=3 🍮]", + '$[bounce 🍮] $[bounce.speed=3s 🍮] $[bounce.delay=3s 🍮] $[bounce.loop=3 🍮]', ); const preview_shake = ref( - "$[shake 🍮] $[shake.speed=3s 🍮] $[shake.delay=3s 🍮] $[shake.loop=3 🍮]", + '$[shake 🍮] $[shake.speed=3s 🍮] $[shake.delay=3s 🍮] $[shake.loop=3 🍮]', ); const preview_twitch = ref( - "$[twitch 🍮] $[twitch.speed=3s 🍮] $[twitch.delay=3s 🍮] $[twitch.loop=3 🍮]", + '$[twitch 🍮] $[twitch.speed=3s 🍮] $[twitch.delay=3s 🍮] $[twitch.loop=3 🍮]', ); const preview_spin = ref( - "$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=3s 🍮] $[spin.delay=3s 🍮] $[spin.loop=3 🍮]", + '$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=3s 🍮] $[spin.delay=3s 🍮] $[spin.loop=3 🍮]', ); const preview_flip = ref( `$[flip ${i18n.ts._mfm.dummy}]\n$[flip.v ${i18n.ts._mfm.dummy}]\n$[flip.h,v ${i18n.ts._mfm.dummy}]`, @@ -420,25 +433,26 @@ const preview_flip = ref( const preview_font = ref( `$[font.serif ${i18n.ts._mfm.dummy}]\n$[font.monospace ${i18n.ts._mfm.dummy}]`, ); -const preview_x2 = ref("$[x2 🍮]"); -const preview_x3 = ref("$[x3 🍮]"); -const preview_x4 = ref("$[x4 🍮]"); +const preview_x2 = ref('$[x2 🍮]'); +const preview_x3 = ref('$[x3 🍮]'); +const preview_x4 = ref('$[x4 🍮]'); const preview_blur = ref(`$[blur ${i18n.ts._mfm.dummy}]`); const preview_rainbow = ref( - "$[rainbow 🍮] $[rainbow.speed=3s 🍮] $[rainbow.delay=3s 🍮] $[rainbow.loop=3 🍮]", + '$[rainbow 🍮] $[rainbow.speed=3s 🍮] $[rainbow.delay=3s 🍮] $[rainbow.loop=3 🍮]', ); -const preview_sparkle = ref("$[sparkle 🍮]"); +const preview_sparkle = ref('$[sparkle 🍮]'); const preview_rotate = ref( - "$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]", + '$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]', ); -const preview_position = ref("$[position.y=-1 🍮]\n$[position.x=-1 🍮]"); +const preview_position = ref('$[position.y=-1 🍮]\n$[position.x=-1 🍮]'); +const preview_followmouse = ref('$[followmouse.x 🍮]\n$[followmouse.x,y,rotateByVelocity,speed=0.4 🍮]'); const preview_scale = ref( - "$[scale.x=1.3 🍮]\n$[scale.x=1.5,y=3 🍮]\n$[scale.y=0.3 🍮]", + '$[scale.x=1.3 🍮]\n$[scale.x=1.5,y=3 🍮]\n$[scale.y=0.3 🍮]', ); -const preview_fg = ref("$[fg.color=eb6f92 Text color]"); -const preview_bg = ref("$[bg.color=31748f Background color]"); +const preview_fg = ref('$[fg.color=eb6f92 Text color]'); +const preview_bg = ref('$[bg.color=31748f Background color]'); const preview_plain = ref( - "<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>", + '<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>', ); </script> diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index f8b5fcfedc..2f699ccd84 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -6,6 +6,7 @@ import { VNode, h, defineAsyncComponent, SetupContext } from 'vue'; import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; +import CkFollowMouse from '../CkFollowMouse.vue'; import MkUrl from '@/components/global/MkUrl.vue'; import MkTime from '@/components/global/MkTime.vue'; import MkLink from '@/components/MkLink.vue'; @@ -232,6 +233,28 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven style = `transform: rotate(${degrees}deg); transform-origin: center center;`; break; } + case 'followmouse': { + // Make sure advanced MFM is on and that reduced motion is off + if (!useAnim) { + style = ''; + break; + } + + let x = (!!token.props.args.x); + let y = (!!token.props.args.y); + + if (!x && !y) { + x = true; + y = true; + } + + return h(CkFollowMouse, { + x: x, + y: y, + speed: validTime(token.props.args.speed) ?? '0.1s', + rotateByVelocity: !!token.props.args.rotateByVelocity, + }, genEl(token.children, scale)); + } case 'position': { if (!defaultStore.state.advancedMfm) break; const x = safeParseFloat(token.props.args.x) ?? 0; diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index ad798067b3..4c7df613a5 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -162,7 +162,7 @@ export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://launcher.moe/error.png'; export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://launcher.moe/missingpage.webp'; export const DEFAULT_INFO_IMAGE_URL = 'https://launcher.moe/nothinghere.png'; -export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime']; +export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime', 'followmouse']; export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = { tada: ['speed=', 'delay='], jelly: ['speed=', 'delay='], @@ -179,11 +179,12 @@ export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = { position: ['x=', 'y='], fg: ['color='], bg: ['color='], - border: ['width=', 'style=', 'color=', 'radius=', 'noclip'], + border: ['width=', 'style=', 'color=', 'radius=', 'noclip'], font: ['serif', 'monospace', 'cursive', 'fantasy', 'emoji', 'math'], blur: [], rainbow: ['speed=', 'delay='], rotate: ['deg='], ruby: [], unixtime: [], + followmouse: ['x', 'y', 'rotateByVelocity', 'speed='], }; From f9a7cd0daa869b23b100f6d32e71f0faaa7873fc Mon Sep 17 00:00:00 2001 From: Leah <kevinlukej@gmail.com> Date: Thu, 23 May 2024 21:40:25 +0000 Subject: [PATCH 10/17] Ported firefish crop and fade mfm --- locales/en-US.yml | 4 +++ .../frontend/src/components/MkMfmWindow.vue | 26 ++++++++++++++- .../global/MkMisskeyFlavoredMarkdown.ts | 32 +++++++++++++++++++ packages/frontend/src/const.ts | 4 ++- packages/frontend/src/style.scss | 9 ++++++ 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index 07173c131e..c442d41c1e 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -2543,12 +2543,16 @@ _mfm: rotateDescription: "Turns content by a specified angle." position: "Position" positionDescription: "Move content by a specified amount." + crop: "Crop" + cropDescription: "Crop content." followMouse: "Follow Mouse" followMouseDescription: "Content will follow the mouse. On mobile it will follow wherever the user taps." scale: "Scale" scaleDescription: "Scale content by a specified amount." foreground: "Foreground color" foregroundDescription: "Change the foreground color of text." + fade: 'Fade' + fadeDescription: 'Fade text in and out.' background: "Background color" backgroundDescription: "Change the background color of text." plain: "Plain" diff --git a/packages/frontend/src/components/MkMfmWindow.vue b/packages/frontend/src/components/MkMfmWindow.vue index fd23eb0097..a742ad184c 100644 --- a/packages/frontend/src/components/MkMfmWindow.vue +++ b/packages/frontend/src/components/MkMfmWindow.vue @@ -304,6 +304,16 @@ </div> </div> </div> + <div class="section _block"> + <div class="title">{{ i18n.ts._mfm.crop }}</div> + <div class="content"> + <p>{{ i18n.ts._mfm.cropDescription }}</p> + <div class="preview"> + <Mfm :text="preview_crop" /> + <MkTextarea v-model="preview_crop"><span>MFM</span></MkTextarea> + </div> + </div> + </div> <div class="section _block"> <div class="title">{{ i18n.ts._mfm.position }}</div> <div class="content"> @@ -316,7 +326,7 @@ </div> <div class="section _block" style="overflow: hidden"> <div class="title">{{ i18n.ts._mfm.followMouse }}</div> - <MkInfo warn>{{ i18n.ts._mfm.uncommonFeature }}</MkInfo> + <MkInfo warn>{{ i18n.ts._mfm.uncommonFeature }}</MkInfo> <br/> <div class="content"> <p>{{ i18n.ts._mfm.followMouseDescription }}</p> @@ -336,6 +346,16 @@ </div> </div> </div> + <div class="section _block"> + <div class="title">{{ i18n.ts._mfm.fade }}</div> + <div class="content"> + <p>{{ i18n.ts._mfm.fadeDescription }}</p> + <div class="preview"> + <Mfm :text="preview_fade" /> + <MkTextarea v-model="preview_fade"><span>MFM</span></MkTextarea> + </div> + </div> + </div> <div class="section _block"> <div class="title">{{ i18n.ts._mfm.foreground }}</div> <div class="content"> @@ -445,6 +465,9 @@ const preview_rotate = ref( '$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]', ); const preview_position = ref('$[position.y=-1 🍮]\n$[position.x=-1 🍮]'); +const preview_crop = ref( + "$[crop.top=50 🍮] $[crop.right=50 🍮] $[crop.bottom=50 🍮] $[crop.left=50 🍮]", +); const preview_followmouse = ref('$[followmouse.x 🍮]\n$[followmouse.x,y,rotateByVelocity,speed=0.4 🍮]'); const preview_scale = ref( '$[scale.x=1.3 🍮]\n$[scale.x=1.5,y=3 🍮]\n$[scale.y=0.3 🍮]', @@ -454,6 +477,7 @@ const preview_bg = ref('$[bg.color=31748f Background color]'); const preview_plain = ref( '<plain>**bold** @mention #hashtag `code` $[x2 🍮]</plain>', ); +const preview_fade = ref(`$[fade 🍮] $[fade.out 🍮] $[fade.speed=3s 🍮] $[fade.delay=3s 🍮]`); </script> <style lang="scss" scoped> diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts index 2f699ccd84..69fe41e2d5 100644 --- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts +++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts @@ -228,6 +228,22 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven } return h(MkSparkle, {}, genEl(token.children, scale)); } + case 'fade': { + // Dont run with reduced motion on + if (!defaultStore.state.animation) { + style = ''; + break; + } + + const direction = token.props.args.out + ? 'alternate-reverse' + : 'alternate'; + const speed = validTime(token.props.args.speed) ?? '1.5s'; + const delay = validTime(token.props.args.delay) ?? '0s'; + const loop = safeParseFloat(token.props.args.loop) ?? 'infinite'; + style = `animation: mfm-fade ${speed} ${delay} linear ${loop}; animation-direction: ${direction};`; + break; + } case 'rotate': { const degrees = safeParseFloat(token.props.args.deg) ?? 90; style = `transform: rotate(${degrees}deg); transform-origin: center center;`; @@ -262,6 +278,22 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven style = `transform: translateX(${x}em) translateY(${y}em);`; break; } + case 'crop': { + const top = Number.parseFloat( + (token.props.args.top ?? '0').toString(), + ); + const right = Number.parseFloat( + (token.props.args.right ?? '0').toString(), + ); + const bottom = Number.parseFloat( + (token.props.args.bottom ?? '0').toString(), + ); + const left = Number.parseFloat( + (token.props.args.left ?? '0').toString(), + ); + style = `clip-path: inset(${top}% ${right}% ${bottom}% ${left}%);`; + break; + } case 'scale': { if (!defaultStore.state.advancedMfm) { style = ''; diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts index 4c7df613a5..5109c34c02 100644 --- a/packages/frontend/src/const.ts +++ b/packages/frontend/src/const.ts @@ -162,7 +162,7 @@ export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://launcher.moe/error.png'; export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://launcher.moe/missingpage.webp'; export const DEFAULT_INFO_IMAGE_URL = 'https://launcher.moe/nothinghere.png'; -export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime', 'followmouse']; +export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime', 'crop', 'fade', 'followmouse']; export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = { tada: ['speed=', 'delay='], jelly: ['speed=', 'delay='], @@ -186,5 +186,7 @@ export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = { rotate: ['deg='], ruby: [], unixtime: [], + fade: ['speed=', 'delay=', 'loop=', 'out'], + crop: ['top=', 'bottom=', 'left=', 'right='], followmouse: ['x', 'y', 'rotateByVelocity', 'speed='], }; diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index d876009961..dbf7d3ccef 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -698,3 +698,12 @@ rt { 0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); } 100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); } } + +@keyframes mfm-fade { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} \ No newline at end of file From d27ce442eab9ae8e2f4ddff1e8ca0d0412e73046 Mon Sep 17 00:00:00 2001 From: dakkar <dakkar@thenautilus.net> Date: Thu, 23 May 2024 21:56:28 +0000 Subject: [PATCH 11/17] more timeline filters - #228 --- .../server/api/endpoints/channels/timeline.ts | 31 +++++++++++++--- .../src/server/api/stream/channels/channel.ts | 8 +++++ .../frontend/src/components/MkTimeline.vue | 4 +++ packages/frontend/src/pages/channel.vue | 35 ++++++++++++++++-- .../frontend/src/pages/user-list-timeline.vue | 36 ++++++++++++++++++- .../frontend/src/ui/deck/channel-column.vue | 26 ++++++++++++-- packages/frontend/src/ui/deck/list-column.vue | 14 +++++++- 7 files changed, 144 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts index 8c55673590..295fc5686c 100644 --- a/packages/backend/src/server/api/endpoints/channels/timeline.ts +++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts @@ -51,6 +51,12 @@ export const paramDef = { sinceDate: { type: 'integer' }, untilDate: { type: 'integer' }, allowPartial: { type: 'boolean', default: false }, // true is recommended but for compatibility false by default + withRenotes: { type: 'boolean', default: true }, + withFiles: { + type: 'boolean', + default: false, + description: 'Only show notes that have attached files.', + }, }, required: ['channelId'], } as const; @@ -89,7 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (me) this.activeUsersChart.read(me); if (!serverSettings.enableFanoutTimeline) { - return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id }, me), me); + return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id, withFiles: ps.withFiles, withRenotes: ps.withRenotes }, me), me); } return await this.fanoutTimelineEndpointService.timeline({ @@ -100,9 +106,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- me, useDbFallback: true, redisTimelines: [`channelTimeline:${channel.id}`], - excludePureRenotes: false, + excludePureRenotes: !ps.withRenotes, + excludeNoFiles: ps.withFiles, dbFallback: async (untilId, sinceId, limit) => { - return await this.getFromDb({ untilId, sinceId, limit, channelId: channel.id }, me); + return await this.getFromDb({ untilId, sinceId, limit, channelId: channel.id, withFiles: ps.withFiles, withRenotes: ps.withRenotes }, me); }, }); }); @@ -112,7 +119,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- untilId: string | null, sinceId: string | null, limit: number, - channelId: string + channelId: string, + withFiles: boolean, + withRenotes: boolean, }, me: MiLocalUser | null) { //#region fallback to database const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) @@ -128,6 +137,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- this.queryService.generateMutedUserQuery(query, me); this.queryService.generateBlockedUserQuery(query, me); } + + if (ps.withRenotes === false) { + query.andWhere(new Brackets(qb => { + qb.orWhere('note.renoteId IS NULL'); + qb.orWhere(new Brackets(qb => { + qb.orWhere('note.text IS NOT NULL'); + qb.orWhere('note.fileIds != \'{}\''); + })); + })); + } + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } //#endregion return await query.limit(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 90ee1ecda5..61bb4bfae2 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -15,6 +15,8 @@ class ChannelChannel extends Channel { public static shouldShare = false; public static requireCredential = false as const; private channelId: string; + private withFiles: boolean; + private withRenotes: boolean; constructor( private noteEntityService: NoteEntityService, @@ -29,6 +31,8 @@ class ChannelChannel extends Channel { @bindThis public async init(params: any) { this.channelId = params.channelId as string; + this.withFiles = params.withFiles ?? false; + this.withRenotes = params.withRenotes ?? true; // Subscribe stream this.subscriber.on('notesStream', this.onNote); @@ -38,6 +42,10 @@ class ChannelChannel extends Channel { private async onNote(note: Packed<'Note'>) { if (note.channelId !== this.channelId) return; + if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return; + + if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する if (isUserRelated(note, this.userIdsWhoMeMuting)) return; // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index 1c14174a37..0f7eb3b86c 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -154,6 +154,8 @@ function connectChannel() { } else if (props.src === 'channel') { if (props.channel == null) return; connection = stream.useChannel('channel', { + withRenotes: props.withRenotes, + withFiles: props.onlyFiles ? true : undefined, channelId: props.channel, }); } else if (props.src === 'role') { @@ -234,6 +236,8 @@ function updatePaginationQuery() { } else if (props.src === 'channel') { endpoint = 'channels/timeline'; query = { + withRenotes: props.withRenotes, + withFiles: props.onlyFiles ? true : undefined, channelId: props.channel, }; } else if (props.src === 'role') { diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 881acd0197..ee081d07ee 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only <!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる --> <MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/> - <MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/> + <MkTimeline :key="channelId + withRenotes + onlyFiles" src="channel" :channel="channelId" :withRenotes="withRenotes" :onlyFiles="onlyFiles" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/> </div> <div v-else-if="tab === 'featured'" key="featured"> <MkNotes :pagination="featuredPagination"/> @@ -95,6 +95,7 @@ import { isSupportShare } from '@/scripts/navigator.js'; import copyToClipboard from '@/scripts/copy-to-clipboard.js'; import { miLocalStorage } from '@/local-storage.js'; import { useRouter } from '@/router/supplier.js'; +import { deepMerge } from '@/scripts/merge.js'; const router = useRouter(); @@ -116,6 +117,15 @@ const featuredPagination = computed(() => ({ channelId: props.channelId, }, })); +const withRenotes = computed<boolean>({ + get: () => defaultStore.reactiveState.tl.value.filter.withRenotes, + set: (x) => saveTlFilter('withRenotes', x), +}); + +const onlyFiles = computed<boolean>({ + get: () => defaultStore.reactiveState.tl.value.filter.onlyFiles, + set: (x) => saveTlFilter('onlyFiles', x), +}); watch(() => props.channelId, async () => { channel.value = await misskeyApi('channels/show', { @@ -136,6 +146,13 @@ watch(() => props.channelId, async () => { } }, { immediate: true }); +function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) { + if (key !== 'withReplies' || $i) { + const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl); + defaultStore.set('tl', out); + } +} + function edit() { router.push(`/channels/${channel.value?.id}/edit`); } @@ -192,7 +209,21 @@ async function search() { const headerActions = computed(() => { if (channel.value && channel.value.userId) { - const headerItems: PageHeaderItem[] = []; + const headerItems: PageHeaderItem[] = [{ + icon: 'ph-dots-three ph-bold ph-lg', + text: i18n.ts.options, + handler: (ev) => { + os.popupMenu([{ + type: 'switch', + text: i18n.ts.showRenotes, + ref: withRenotes, + }, { + type: 'switch', + text: i18n.ts.fileAttachedOnly, + ref: onlyFiles, + }], ev.currentTarget ?? ev.target); + }, + }]; headerItems.push({ icon: 'ph-share-network ph-bold ph-lg', diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue index dd0b7fb675..b2d52b013c 100644 --- a/packages/frontend/src/pages/user-list-timeline.vue +++ b/packages/frontend/src/pages/user-list-timeline.vue @@ -11,10 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div :class="$style.tl"> <MkTimeline - ref="tlEl" :key="listId" + ref="tlEl" :key="listId + withRenotes + onlyFiles" src="list" :list="listId" :sound="true" + :withRenotes="withRenotes" + :onlyFiles="onlyFiles" @queue="queueUpdated" /> </div> @@ -32,6 +34,9 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { useRouter } from '@/router/supplier.js'; +import { defaultStore } from '@/store.js'; +import { deepMerge } from '@/scripts/merge.js'; +import * as os from '@/os.js'; const router = useRouter(); @@ -43,6 +48,21 @@ const list = ref<Misskey.entities.UserList | null>(null); const queue = ref(0); const tlEl = shallowRef<InstanceType<typeof MkTimeline>>(); const rootEl = shallowRef<HTMLElement>(); +const withRenotes = computed<boolean>({ + get: () => defaultStore.reactiveState.tl.value.filter.withRenotes, + set: (x) => saveTlFilter('withRenotes', x), +}); +const onlyFiles = computed<boolean>({ + get: () => defaultStore.reactiveState.tl.value.filter.onlyFiles, + set: (x) => saveTlFilter('onlyFiles', x), +}); + +function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) { + if (key !== 'withReplies' || $i) { + const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl); + defaultStore.set('tl', out); + } +} watch(() => props.listId, async () => { list.value = await misskeyApi('users/lists/show', { @@ -63,6 +83,20 @@ function settings() { } const headerActions = computed(() => list.value ? [{ + icon: 'ph-dots-three ph-bold ph-lg', + text: i18n.ts.options, + handler: (ev) => { + os.popupMenu([{ + type: 'switch', + text: i18n.ts.showRenotes, + ref: withRenotes, + }, { + type: 'switch', + text: i18n.ts.fileAttachedOnly, + ref: onlyFiles, + }], ev.currentTarget ?? ev.target); + }, +}, { icon: 'ph-gear ph-bold ph-lg', text: i18n.ts.settings, handler: settings, diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index 984de82c3f..993be46910 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -13,13 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only <div style="padding: 8px; text-align: center;"> <MkButton primary gradate rounded inline small @click="post"><i class="ph-pencil-simple ph-bold ph-lg"></i></MkButton> </div> - <MkTimeline ref="timeline" src="channel" :channel="column.channelId"/> + <MkTimeline ref="timeline" src="channel" :channel="column.channelId" :key="column.channelId + column.withRenotes + column.onlyFiles" :withRenotes="withRenotes" :onlyFiles="onlyFiles"/> </template> </XColumn> </template> <script lang="ts" setup> -import { shallowRef } from 'vue'; +import { watch, ref, shallowRef } from 'vue'; import * as Misskey from 'misskey-js'; import XColumn from './column.vue'; import { updateColumn, Column } from './deck-store.js'; @@ -36,6 +36,20 @@ const props = defineProps<{ const timeline = shallowRef<InstanceType<typeof MkTimeline>>(); const channel = shallowRef<Misskey.entities.Channel>(); +const withRenotes = ref(props.column.withRenotes ?? true); +const onlyFiles = ref(props.column.onlyFiles ?? false); + +watch(withRenotes, v => { + updateColumn(props.column.id, { + withRenotes: v, + }); +}); + +watch(onlyFiles, v => { + updateColumn(props.column.id, { + onlyFiles: v, + }); +}); if (props.column.channelId == null) { setChannel(); @@ -75,5 +89,13 @@ const menu = [{ icon: 'ph-pencil-simple ph-bold ph-lg', text: i18n.ts.selectChannel, action: setChannel, +}, { + type: 'switch', + text: i18n.ts.showRenotes, + ref: withRenotes, +}, { + type: 'switch', + text: i18n.ts.fileAttachedOnly, + ref: onlyFiles, }]; </script> diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index 128562823b..f7988ed1b7 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ph-list ph-bold ph-lg"></i><span style="margin-left: 8px;">{{ column.name }}</span> </template> - <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes"/> + <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :key="column.listId + column.withRenotes + column.onlyFiles" :withRenotes="withRenotes" :onlyFiles="onlyFiles"/> </XColumn> </template> @@ -29,6 +29,7 @@ const props = defineProps<{ const timeline = shallowRef<InstanceType<typeof MkTimeline>>(); const withRenotes = ref(props.column.withRenotes ?? true); +const onlyFiles = ref(props.column.onlyFiles ?? false); if (props.column.listId == null) { setList(); @@ -40,6 +41,12 @@ watch(withRenotes, v => { }); }); +watch(onlyFiles, v => { + updateColumn(props.column.id, { + onlyFiles: v, + }); +}); + async function setList() { const lists = await misskeyApi('users/lists/list'); const { canceled, result: list } = await os.select({ @@ -75,5 +82,10 @@ const menu = [ text: i18n.ts.showRenotes, ref: withRenotes, }, + { + type: 'switch', + text: i18n.ts.fileAttachedOnly, + ref: onlyFiles, + }, ]; </script> From c42d61f69ba28b82aa41d7d5799c8218881f9138 Mon Sep 17 00:00:00 2001 From: dakkar <dakkar@thenautilus.net> Date: Fri, 24 May 2024 20:47:10 +0000 Subject: [PATCH 12/17] put back button to delete all files for a user - fixes #535 --- packages/frontend/src/pages/admin-user.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 3beaf5d08b..eb9ca602ab 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -111,7 +111,8 @@ SPDX-License-Identifier: AGPL-3.0-only <div> <MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserAvatar"><i class="ph-user-circle ph-bold ph-lg"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton> - <MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ph-photo ph-bold ph-lg"></i> {{ i18n.ts.unsetUserBanner }}</MkButton> + <MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserBanner"><i class="ph-image ph-bold ph-lg"></i> {{ i18n.ts.unsetUserBanner }}</MkButton> + <MkButton v-if="iAmModerator" inline danger @click="deleteAllFiles"><i class="ph-cloud ph-bold ph-lg"></i> {{ i18n.ts.deleteAllFiles }}</MkButton> </div> <MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton> </div> From 981975404de5672e7dac5f04b37f66ffc048bb93 Mon Sep 17 00:00:00 2001 From: Marie <marie@kaifa.ch> Date: Thu, 30 May 2024 12:08:30 +0000 Subject: [PATCH 13/17] Fix Visiblity issue --- packages/backend/src/server/api/mastodon/converters.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 326d3a1d5c..0918fc74f9 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -146,8 +146,8 @@ export class MastoConverters { display_name: user.name ?? user.username, locked: user.isLocked, created_at: this.idService.parse(user.id).date.toISOString(), - followers_count: user.followersCount, - following_count: user.followingCount, + followers_count: profile?.ffVisibility === 'public' ? user.followersCount : 0, + following_count: profile?.ffVisibility === 'public' ? user.followingCount : 0, statuses_count: user.notesCount, note: profile?.description ?? '', url: user.uri ?? acctUrl, From d7bd112b37f573790bcde2d7208960b190e96c48 Mon Sep 17 00:00:00 2001 From: Marie <marie@kaifa.ch> Date: Thu, 30 May 2024 13:03:54 +0000 Subject: [PATCH 14/17] fix incorrect variable name --- packages/backend/src/server/api/mastodon/converters.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts index 0918fc74f9..ea219b933d 100644 --- a/packages/backend/src/server/api/mastodon/converters.ts +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -146,8 +146,8 @@ export class MastoConverters { display_name: user.name ?? user.username, locked: user.isLocked, created_at: this.idService.parse(user.id).date.toISOString(), - followers_count: profile?.ffVisibility === 'public' ? user.followersCount : 0, - following_count: profile?.ffVisibility === 'public' ? user.followingCount : 0, + followers_count: profile?.followersVisibility === 'public' ? user.followersCount : 0, + following_count: profile?.followingVisibility === 'public' ? user.followingCount : 0, statuses_count: user.notesCount, note: profile?.description ?? '', url: user.uri ?? acctUrl, From cebad801e21500f87b4978bb97bf0df025dceb9d Mon Sep 17 00:00:00 2001 From: fEmber <acomputerdog@gmail.com> Date: Thu, 30 May 2024 13:17:51 +0000 Subject: [PATCH 15/17] fix: don't create duplicate workers when clustering is disabled --- packages/backend/src/boot/entry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts index ae74a43c84..3882686fdc 100644 --- a/packages/backend/src/boot/entry.ts +++ b/packages/backend/src/boot/entry.ts @@ -75,7 +75,7 @@ async function main() { ev.mount(); } } - if (cluster.isWorker || envOption.disableClustering) { + if (cluster.isWorker) { await workerMain(); } From 2532fea702e185aa8a7b98f167535cd679da34bc Mon Sep 17 00:00:00 2001 From: fEmber <acomputerdog@gmail.com> Date: Thu, 30 May 2024 13:18:44 +0000 Subject: [PATCH 16/17] fix: start only one instance of ChartManagementService scheduled job --- packages/backend/src/boot/common.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts index 268c07582d..18a2ab149a 100644 --- a/packages/backend/src/boot/common.ts +++ b/packages/backend/src/boot/common.ts @@ -36,7 +36,6 @@ export async function jobQueue() { }); jobQueue.get(QueueProcessorService).start(); - jobQueue.get(ChartManagementService).start(); return jobQueue; } From 3050dcbef7f43e7ad2a07a60a6da175023527d89 Mon Sep 17 00:00:00 2001 From: dakkar <dakkar@thenautilus.net> Date: Thu, 30 May 2024 14:22:00 +0000 Subject: [PATCH 17/17] set the correct "marked an NSFW" when loading admin-user --- packages/backend/src/core/DriveService.ts | 3 ++- packages/frontend/src/pages/admin-user.vue | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index f64568ee9a..4203b03c74 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -632,7 +632,8 @@ export class DriveService { @bindThis public async updateFile(file: MiDriveFile, values: Partial<MiDriveFile>, updater: MiUser) { - const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw; + const profile = await this.userProfilesRepository.findOneBy({ userId: file.userId }); + const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw || (profile !== null && profile!.alwaysMarkNsfw); if (values.name != null && !this.driveFileEntityService.validateFileName(values.name)) { throw new DriveService.InvalidFileNameError(); diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 3beaf5d08b..52272e7365 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -265,6 +265,7 @@ function createFetcher() { moderator.value = info.value.isModerator; silenced.value = info.value.isSilenced; approved.value = info.value.approved; + markedAsNSFW.value = info.value.alwaysMarkNsfw; suspended.value = info.value.isSuspended; moderationNote.value = info.value.moderationNote;