diff options
| author | misskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com> | 2025-10-24 06:31:35 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-24 06:31:35 +0000 |
| commit | b4e16c83e2823f6348842c23c48de2caae52563a (patch) | |
| tree | e4efaf0f2910491d039aab59d544a92836b77266 /packages/frontend/src/components | |
| parent | Merge pull request #16591 from misskey-dev/develop (diff) | |
| parent | Release: 2025.10.1 (diff) | |
| download | misskey-b4e16c83e2823f6348842c23c48de2caae52563a.tar.gz misskey-b4e16c83e2823f6348842c23c48de2caae52563a.tar.bz2 misskey-b4e16c83e2823f6348842c23c48de2caae52563a.zip | |
Merge pull request #16629 from misskey-dev/develop
Release: 2025.10.1
Diffstat (limited to 'packages/frontend/src/components')
13 files changed, 353 insertions, 340 deletions
diff --git a/packages/frontend/src/components/MkAnimBg.fragment.glsl b/packages/frontend/src/components/MkAnimBg.fragment.glsl new file mode 100644 index 0000000000..d40872bb7a --- /dev/null +++ b/packages/frontend/src/components/MkAnimBg.fragment.glsl @@ -0,0 +1,111 @@ +#version 300 es +precision mediump float; + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec2 mod289(vec2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec3 permute(vec3 x) { + return mod289(((x*34.0)+1.0)*x); +} + +float snoise(vec2 v) { + const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); + + vec2 i = floor(v + dot(v, C.yy)); + vec2 x0 = v - i + dot(i, C.xx); + + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + i = mod289(i); + vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); + + vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0); + m = m*m; + m = m*m; + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +in vec2 in_uv; +uniform float u_time; +uniform vec2 u_resolution; +uniform float u_spread; +uniform float u_speed; +uniform float u_warp; +uniform float u_focus; +uniform float u_itensity; +out vec4 out_color; + +float circle(in vec2 _pos, in vec2 _origin, in float _radius) { + float SPREAD = 0.7 * u_spread; + float SPEED = 0.00055 * u_speed; + float WARP = 1.5 * u_warp; + float FOCUS = 1.15 * u_focus; + + vec2 dist = _pos - _origin; + + float distortion = snoise(vec2( + _pos.x * 1.587 * WARP + u_time * SPEED * 0.5, + _pos.y * 1.192 * WARP + u_time * SPEED * 0.3 + )) * 0.5 + 0.5; + + float feather = 0.01 + SPREAD * pow(distortion, FOCUS); + + return 1.0 - smoothstep( + _radius - (_radius * feather), + _radius + (_radius * feather), + dot( dist, dist ) * 4.0 + ); +} + +void main() { + vec3 green = vec3(1.0) - vec3(153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0); + vec3 purple = vec3(1.0) - vec3(195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0); + vec3 orange = vec3(1.0) - vec3(255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0); + + float ratio = u_resolution.x / u_resolution.y; + + vec2 uv = vec2(in_uv.x, in_uv.y / ratio) * 0.5 + 0.5; + + vec3 color = vec3(0.0); + + float greenMix = snoise(in_uv * 1.31 + u_time * 0.8 * 0.00017) * 0.5 + 0.5; + float purpleMix = snoise(in_uv * 1.26 + u_time * 0.8 * -0.0001) * 0.5 + 0.5; + float orangeMix = snoise(in_uv * 1.34 + u_time * 0.8 * 0.00015) * 0.5 + 0.5; + + float alphaOne = 0.35 + 0.65 * pow(snoise(vec2(u_time * 0.00012, uv.x)) * 0.5 + 0.5, 1.2); + float alphaTwo = 0.35 + 0.65 * pow(snoise(vec2((u_time + 1561.0) * 0.00014, uv.x )) * 0.5 + 0.5, 1.2); + float alphaThree = 0.35 + 0.65 * pow(snoise(vec2((u_time + 3917.0) * 0.00013, uv.x )) * 0.5 + 0.5, 1.2); + + color += vec3(circle(uv, vec2(0.22 + sin(u_time * 0.000201) * 0.06, 0.80 + cos(u_time * 0.000151) * 0.06), 0.15)) * alphaOne * (purple * purpleMix + orange * orangeMix); + color += vec3(circle(uv, vec2(0.90 + cos(u_time * 0.000166) * 0.06, 0.42 + sin(u_time * 0.000138) * 0.06), 0.18)) * alphaTwo * (green * greenMix + purple * purpleMix); + color += vec3(circle(uv, vec2(0.19 + sin(u_time * 0.000112) * 0.06, 0.25 + sin(u_time * 0.000192) * 0.06), 0.09)) * alphaThree * (orange * orangeMix); + + color *= u_itensity + 1.0 * pow(snoise(vec2(in_uv.y + u_time * 0.00013, in_uv.x + u_time * -0.00009)) * 0.5 + 0.5, 2.0); + + vec3 inverted = vec3(1.0) - color; + out_color = vec4(color, max(max(color.x, color.y), color.z)); +} diff --git a/packages/frontend/src/components/MkAnimBg.vertex.glsl b/packages/frontend/src/components/MkAnimBg.vertex.glsl new file mode 100644 index 0000000000..56d6b017b1 --- /dev/null +++ b/packages/frontend/src/components/MkAnimBg.vertex.glsl @@ -0,0 +1,15 @@ +#version 300 es + +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +in vec2 position; +uniform vec2 u_scale; +out vec2 in_uv; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + in_uv = position / u_scale; +} diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue index 0e1018dcbf..bcdc604bb8 100644 --- a/packages/frontend/src/components/MkAnimBg.vue +++ b/packages/frontend/src/components/MkAnimBg.vue @@ -10,6 +10,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, onUnmounted, useTemplateRef } from 'vue'; import isChromatic from 'chromatic/isChromatic'; +import vertexShaderSource from './MkAnimBg.vertex.glsl'; +import fragmentShaderSource from './MkAnimBg.fragment.glsl'; import { initShaderProgram } from '@/utility/webgl.js'; const canvasEl = useTemplateRef('canvasEl'); @@ -42,126 +44,7 @@ onMounted(() => { const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); - const shaderProgram = initShaderProgram(gl, `#version 300 es - in vec2 position; - uniform vec2 u_scale; - out vec2 in_uv; - - void main() { - gl_Position = vec4(position, 0.0, 1.0); - in_uv = position / u_scale; - } - `, `#version 300 es - precision mediump float; - - vec3 mod289(vec3 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; - } - - vec2 mod289(vec2 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; - } - - vec3 permute(vec3 x) { - return mod289(((x*34.0)+1.0)*x); - } - - float snoise(vec2 v) { - const vec4 C = vec4(0.211324865405187, - 0.366025403784439, - -0.577350269189626, - 0.024390243902439); - - vec2 i = floor(v + dot(v, C.yy) ); - vec2 x0 = v - i + dot(i, C.xx); - - vec2 i1; - i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - vec4 x12 = x0.xyxy + C.xxzz; - x12.xy -= i1; - - i = mod289(i); - vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) - + i.x + vec3(0.0, i1.x, 1.0 )); - - vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); - m = m*m ; - m = m*m ; - - vec3 x = 2.0 * fract(p * C.www) - 1.0; - vec3 h = abs(x) - 0.5; - vec3 ox = floor(x + 0.5); - vec3 a0 = x - ox; - - m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); - - vec3 g; - g.x = a0.x * x0.x + h.x * x0.y; - g.yz = a0.yz * x12.xz + h.yz * x12.yw; - return 130.0 * dot(m, g); - } - - in vec2 in_uv; - uniform float u_time; - uniform vec2 u_resolution; - uniform float u_spread; - uniform float u_speed; - uniform float u_warp; - uniform float u_focus; - uniform float u_itensity; - out vec4 out_color; - - float circle( in vec2 _pos, in vec2 _origin, in float _radius ) { - float SPREAD = 0.7 * u_spread; - float SPEED = 0.00055 * u_speed; - float WARP = 1.5 * u_warp; - float FOCUS = 1.15 * u_focus; - - vec2 dist = _pos - _origin; - - float distortion = snoise( vec2( - _pos.x * 1.587 * WARP + u_time * SPEED * 0.5, - _pos.y * 1.192 * WARP + u_time * SPEED * 0.3 - ) ) * 0.5 + 0.5; - - float feather = 0.01 + SPREAD * pow( distortion, FOCUS ); - - return 1.0 - smoothstep( - _radius - ( _radius * feather ), - _radius + ( _radius * feather ), - dot( dist, dist ) * 4.0 - ); - } - - void main() { - vec3 green = vec3( 1.0 ) - vec3( 153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0 ); - vec3 purple = vec3( 1.0 ) - vec3( 195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0 ); - vec3 orange = vec3( 1.0 ) - vec3( 255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0 ); - - float ratio = u_resolution.x / u_resolution.y; - - vec2 uv = vec2( in_uv.x, in_uv.y / ratio ) * 0.5 + 0.5; - - vec3 color = vec3( 0.0 ); - - float greenMix = snoise( in_uv * 1.31 + u_time * 0.8 * 0.00017 ) * 0.5 + 0.5; - float purpleMix = snoise( in_uv * 1.26 + u_time * 0.8 * -0.0001 ) * 0.5 + 0.5; - float orangeMix = snoise( in_uv * 1.34 + u_time * 0.8 * 0.00015 ) * 0.5 + 0.5; - - float alphaOne = 0.35 + 0.65 * pow( snoise( vec2( u_time * 0.00012, uv.x ) ) * 0.5 + 0.5, 1.2 ); - float alphaTwo = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 1561.0 ) * 0.00014, uv.x ) ) * 0.5 + 0.5, 1.2 ); - float alphaThree = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 3917.0 ) * 0.00013, uv.x ) ) * 0.5 + 0.5, 1.2 ); - - color += vec3( circle( uv, vec2( 0.22 + sin( u_time * 0.000201 ) * 0.06, 0.80 + cos( u_time * 0.000151 ) * 0.06 ), 0.15 ) ) * alphaOne * ( purple * purpleMix + orange * orangeMix ); - color += vec3( circle( uv, vec2( 0.90 + cos( u_time * 0.000166 ) * 0.06, 0.42 + sin( u_time * 0.000138 ) * 0.06 ), 0.18 ) ) * alphaTwo * ( green * greenMix + purple * purpleMix ); - color += vec3( circle( uv, vec2( 0.19 + sin( u_time * 0.000112 ) * 0.06, 0.25 + sin( u_time * 0.000192 ) * 0.06 ), 0.09 ) ) * alphaThree * ( orange * orangeMix ); - - color *= u_itensity + 1.0 * pow( snoise( vec2( in_uv.y + u_time * 0.00013, in_uv.x + u_time * -0.00009 ) ) * 0.5 + 0.5, 2.0 ); - - vec3 inverted = vec3( 1.0 ) - color; - out_color = vec4(color, max(max(color.x, color.y), color.z)); - } - `); + const shaderProgram = initShaderProgram(gl, vertexShaderSource, fragmentShaderSource); if (shaderProgram == null) return; gl.useProgram(shaderProgram); diff --git a/packages/frontend/src/components/MkImageEffectorDialog.vue b/packages/frontend/src/components/MkImageEffectorDialog.vue index 5ce514f93e..0fc8fa9dcc 100644 --- a/packages/frontend/src/components/MkImageEffectorDialog.vue +++ b/packages/frontend/src/components/MkImageEffectorDialog.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.root"> <div :class="$style.container"> <div :class="$style.preview"> - <canvas ref="canvasEl" :class="$style.previewCanvas" @pointerdown="onImagePointerdown"></canvas> + <canvas ref="canvasEl" :class="$style.previewCanvas" @pointerdown.prevent.stop="onImagePointerdown"></canvas> <div :class="$style.previewContainer"> <div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div> <div class="_acrylic" :class="$style.editControls"> @@ -257,8 +257,12 @@ function onImagePointerdown(ev: PointerEvent) { xOffset /= 2; yOffset /= 2; - let startX = ev.offsetX - xOffset; - let startY = ev.offsetY - yOffset; + const rect = canvasEl.value.getBoundingClientRect(); + const pointerOffsetX = ev.clientX - rect.left; + const pointerOffsetY = ev.clientY - rect.top; + + let startX = pointerOffsetX - xOffset; + let startY = pointerOffsetY - yOffset; if (AW / AH < BW / BH) { // 横長 startX = startX / (Math.max(AW, AH) / Math.max(BH / BW, 1)); @@ -311,9 +315,11 @@ function onImagePointerdown(ev: PointerEvent) { }); } - _move(ev.offsetX, ev.offsetY); + _move(ev.clientX, ev.clientY); - function _move(pointerX: number, pointerY: number) { + function _move(pointerClientX: number, pointerClientY: number) { + const pointerX = pointerClientX - rect.left; + const pointerY = pointerClientY - rect.top; let x = pointerX - xOffset; let y = pointerY - yOffset; @@ -340,7 +346,7 @@ function onImagePointerdown(ev: PointerEvent) { } function move(ev: PointerEvent) { - _move(ev.offsetX, ev.offsetY); + _move(ev.clientX, ev.clientY); } function up() { @@ -448,6 +454,7 @@ function onImagePointerdown(ev: PointerEvent) { margin: 20px; box-sizing: border-box; object-fit: contain; + touch-action: none; } .controls { diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 76c65397ae..9fc9c98493 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -57,15 +57,8 @@ const remaining = computed(() => { return Math.floor(Math.max(expiresAtTime.value - now.value, 0) / 1000); }); -const remainingWatchStop = watch(remaining, (to) => { - if (to <= 0) { - showResult.value = true; - remainingWatchStop(); - } -}, { immediate: true }); - const total = computed(() => sum(props.choices.map(x => x.votes))); -const closed = computed(() => remaining.value === 0); +const closed = computed(() => remaining.value <= 0); const isVoted = computed(() => !props.multiple && props.choices.some(c => c.isVoted)); const timer = computed(() => i18n.tsx._poll[ remaining.value >= 86400 ? 'remainingDays' : @@ -78,7 +71,16 @@ const timer = computed(() => i18n.tsx._poll[ d: Math.floor(remaining.value / 86400), })); -const showResult = ref(props.readOnly || isVoted.value); +const showResult = ref(props.readOnly || isVoted.value || closed.value); + +if (!closed.value) { + const closedWatchStop = watch(closed, (isNowClosed) => { + if (isNowClosed) { + showResult.value = true; + closedWatchStop(); + } + }); +} const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({ type: 'lookup', diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index c1b950a6c8..afa70cdbae 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -17,7 +17,6 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-click-anime v-tooltip="i18n.ts.switchAccount" :class="$style.account" class="_button" @click="openAccountMenu"> <img :class="$style.avatar" :src="(postAccount ?? $i).avatarUrl" style="border-radius: 100%;"/> </button> - <button v-if="$i.policies.noteDraftLimit > 0" v-tooltip="(postAccount != null && postAccount.id !== $i.id) ? null : i18n.ts.draftsAndScheduledNotes" class="_button" :class="$style.draftButton" :disabled="postAccount != null && postAccount.id !== $i.id" @click="showDraftMenu"><i class="ti ti-list"></i></button> </div> <div :class="$style.headerRight"> <template v-if="!(targetChannel != null && fixed)"> @@ -141,7 +140,7 @@ import MkInfo from '@/components/MkInfo.vue'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { ensureSignin, notesCount, incNotesCount } from '@/i.js'; -import { getAccounts, openAccountMenu as openAccountMenu_ } from '@/accounts.js'; +import { getAccounts, getAccountMenu } from '@/accounts.js'; import { deepClone } from '@/utility/clone.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { miLocalStorage } from '@/local-storage.js'; @@ -620,6 +619,19 @@ function showOtherSettings() { action: () => { toggleReactionAcceptance(); }, + }, { type: 'divider' }, { + type: 'button', + text: i18n.ts._drafts.saveToDraft, + icon: 'ti ti-cloud-upload', + action: async () => { + if (!canSaveAsServerDraft.value) { + return os.alert({ + type: 'error', + text: i18n.ts._drafts.cannotCreateDraft, + }); + } + saveServerDraft(); + }, }, ...($i.policies.scheduledNoteLimit > 0 ? [{ icon: 'ti ti-calendar-time', text: i18n.ts.schedulePost + '...', @@ -1159,34 +1171,9 @@ function showActions(ev: MouseEvent) { const postAccount = ref<Misskey.entities.UserDetailed | null>(null); -function openAccountMenu(ev: MouseEvent) { +async function openAccountMenu(ev: MouseEvent) { if (props.mock) return; - openAccountMenu_({ - withExtraOperation: false, - includeCurrentAccount: true, - active: postAccount.value != null ? postAccount.value.id : $i.id, - onChoose: (account) => { - if (account.id === $i.id) { - postAccount.value = null; - } else { - postAccount.value = account; - } - }, - }, ev); -} - -function showPerUploadItemMenu(item: UploaderItem, ev: MouseEvent) { - const menu = uploader.getMenu(item); - os.popupMenu(menu, ev.currentTarget ?? ev.target); -} - -function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent) { - const menu = uploader.getMenu(item); - os.contextMenu(menu, ev); -} - -function showDraftMenu(ev: MouseEvent) { function showDraftsDialog(scheduled: boolean) { const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), { scheduled, @@ -1244,34 +1231,44 @@ function showDraftMenu(ev: MouseEvent) { }); } - os.popupMenu([{ - type: 'button', - text: i18n.ts._drafts.saveToDraft, - icon: 'ti ti-cloud-upload', - action: async () => { - if (!canSaveAsServerDraft.value) { - return os.alert({ - type: 'error', - text: i18n.ts._drafts.cannotCreateDraft, - }); + const items = await getAccountMenu({ + withExtraOperation: false, + includeCurrentAccount: true, + active: postAccount.value != null ? postAccount.value.id : $i.id, + onChoose: (account) => { + if (account.id === $i.id) { + postAccount.value = null; + } else { + postAccount.value = account; } - saveServerDraft(); }, - }, { + }); + + os.popupMenu([{ type: 'button', text: i18n.ts._drafts.listDrafts, icon: 'ti ti-cloud-download', action: () => { showDraftsDialog(false); }, - }, { type: 'divider' }, { + }, { type: 'button', text: i18n.ts._drafts.listScheduledNotes, icon: 'ti ti-clock-down', action: () => { showDraftsDialog(true); }, - }], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); + }, { type: 'divider' }, ...items], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); +} + +function showPerUploadItemMenu(item: UploaderItem, ev: MouseEvent) { + const menu = uploader.getMenu(item); + os.popupMenu(menu, ev.currentTarget ?? ev.target); +} + +function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent) { + const menu = uploader.getMenu(item); + os.contextMenu(menu, ev); } async function schedule() { @@ -1422,20 +1419,6 @@ defineExpose({ margin: auto; } -.draftButton { - padding: 8px; - font-size: 90%; - border-radius: 6px; - - &:hover { - background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)); - } - - &:disabled { - background: none; - } -} - .headerRight { display: flex; min-height: 48px; diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index d96f0e2420..7c60288883 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, inject, onMounted, useTemplateRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; -import { getUnicodeEmoji } from '@@/js/emojilist.js'; +import { getUnicodeEmojiOrNull } from '@@/js/emojilist.js'; import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue'; import type { MenuItem } from '@/types/menu'; import XDetails from '@/components/MkReactionsViewer.details.vue'; @@ -60,11 +60,11 @@ const buttonEl = useTemplateRef('buttonEl'); const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, '')); const canToggle = computed(() => { - const emoji = customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction); + const emoji = customEmojisMap.get(emojiName.value) ?? getUnicodeEmojiOrNull(props.reaction); // TODO //return !props.reaction.match(/@\w/) && $i && emoji && checkReactionPermissions($i, props.note, emoji); - return !props.reaction.match(/@\w/) && $i && emoji; + return props.reaction.match(/@\w/) == null && $i != null && emoji != null; }); const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':')); const isLocalCustomEmoji = props.reaction[0] === ':' && props.reaction.includes('@.'); diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index dbc673333c..236afa127c 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -64,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts"> +import type { Awaitable } from '@/types/misc.js'; + export type SuperMenuDef = { title?: string; items: ({ @@ -80,7 +82,7 @@ export type SuperMenuDef = { text: string; danger?: boolean; active?: boolean; - action: (ev: MouseEvent) => void | Promise<void>; + action: (ev: MouseEvent) => Awaitable<void>; } | { type?: 'link'; to: string; diff --git a/packages/frontend/src/components/MkUploaderDialog.vue b/packages/frontend/src/components/MkUploaderDialog.vue index ce098d71e4..8849fa447d 100644 --- a/packages/frontend/src/components/MkUploaderDialog.vue +++ b/packages/frontend/src/components/MkUploaderDialog.vue @@ -33,6 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only <!-- クライアントで検出するMIME typeとサーバーで検出するMIME typeが異なる場合があり、混乱の元になるのでとりあえず隠しとく --> <!-- https://github.com/misskey-dev/misskey/issues/16091 --> + <!-- https://github.com/misskey-dev/misskey/issues/16663 --> <!--<div>{{ i18n.ts._uploader.allowedTypes }}: {{ $i.policies.uploadableFileTypes.join(', ') }}</div>--> </div> </div> diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue index 288293db3f..b34181e5cc 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue @@ -65,6 +65,10 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="layer.repeat"> <template #label>{{ i18n.ts._watermarkEditor.repeat }}</template> </MkSwitch> + + <MkSwitch v-model="layerPreserveBoundingRect"> + <template #label>{{ i18n.ts._watermarkEditor.preserveBoundingRect }}</template> + </MkSwitch> </template> <template v-else-if="layer.type === 'image'"> @@ -129,6 +133,10 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="layer.cover"> <template #label>{{ i18n.ts._watermarkEditor.cover }}</template> </MkSwitch> + + <MkSwitch v-model="layerPreserveBoundingRect"> + <template #label>{{ i18n.ts._watermarkEditor.preserveBoundingRect }}</template> + </MkSwitch> </template> <template v-else-if="layer.type === 'qr'"> @@ -335,7 +343,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script setup lang="ts"> -import { ref, onMounted } from 'vue'; +import { ref, onMounted, computed } from 'vue'; import * as Misskey from 'misskey-js'; import type { WatermarkPreset } from '@/utility/watermark.js'; import { i18n } from '@/i18n.js'; @@ -351,6 +359,20 @@ import { misskeyApi } from '@/utility/misskey-api.js'; const layer = defineModel<WatermarkPreset['layers'][number]>('layer', { required: true }); +const layerPreserveBoundingRect = computed({ + get: () => { + if (layer.value.type === 'text' || layer.value.type === 'image') { + return !layer.value.noBoundingBoxExpansion; + } + return false; + }, + set: (v: boolean) => { + if (layer.value.type === 'text' || layer.value.type === 'image') { + layer.value.noBoundingBoxExpansion = !v; + } + }, +}); + const driveFile = ref<Misskey.entities.DriveFile | null>(null); const driveFileError = ref(false); onMounted(async () => { diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.vue index 0d0488d9bc..3b3f20d8d1 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.vue @@ -90,6 +90,7 @@ function createTextLayer(): WatermarkPreset['layers'][number] { angle: 0, opacity: 0.75, repeat: false, + noBoundingBoxExpansion: false, }; } @@ -104,6 +105,7 @@ function createImageLayer(): WatermarkPreset['layers'][number] { angle: 0, opacity: 0.75, repeat: false, + noBoundingBoxExpansion: false, cover: false, }; } diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 2f4de840db..c4adf440cb 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -62,9 +62,10 @@ import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'v import { scrollToTop } from '@@/js/scroll.js'; import XTabs from './MkPageHeader.tabs.vue'; import { globalEvents } from '@/events.js'; -import { openAccountMenu as openAccountMenu_ } from '@/accounts.js'; +import { getAccountMenu } from '@/accounts.js'; import { $i } from '@/i.js'; import { DI } from '@/di.js'; +import * as os from '@/os.js'; const props = withDefaults(defineProps<PageHeaderProps>(), { tabs: () => ([] as Tab[]), @@ -99,10 +100,12 @@ const top = () => { } }; -function openAccountMenu(ev: MouseEvent) { - openAccountMenu_({ +async function openAccountMenu(ev: MouseEvent) { + const menuItems = await getAccountMenu({ withExtraOperation: true, - }, ev); + }); + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } function onTabClick(): void { diff --git a/packages/frontend/src/components/grid/MkGrid.vue b/packages/frontend/src/components/grid/MkGrid.vue index a175485a7e..96d9e35773 100644 --- a/packages/frontend/src/components/grid/MkGrid.vue +++ b/packages/frontend/src/components/grid/MkGrid.vue @@ -71,7 +71,7 @@ import { import * as os from '@/os.js'; import { createColumn } from '@/components/grid/column.js'; import { createRow, defaultGridRowSetting, resetRow } from '@/components/grid/row.js'; -import { handleKeyEvent } from '@/utility/key-event.js'; +import { makeHotkey } from '@/utility/hotkey.js'; type RowHolder = { row: GridRow, @@ -289,161 +289,143 @@ function onKeyDown(ev: KeyboardEvent) { const max = availableBounds.value; const bounds = rangedBounds.value; - handleKeyEvent(ev, [ - { - code: 'Delete', handler: () => { - if (rangedRows.value.length > 0) { - if (rowSetting.events.delete) { - rowSetting.events.delete(rangedRows.value); - } - } else { - const context = createContext(); - removeDataFromGrid(context, (cell) => { - emitCellValue(cell, undefined); - }); + makeHotkey({ + 'delete': () => { + if (rangedRows.value.length > 0) { + if (rowSetting.events.delete) { + rowSetting.events.delete(rangedRows.value); } - }, - }, - { - code: 'KeyC', modifiers: ['Control'], handler: () => { - const context = createContext(); - copyGridDataToClipboard(data.value, context); - }, - }, - { - code: 'KeyV', modifiers: ['Control'], handler: async () => { - const _cells = cells.value; + } else { const context = createContext(); - await pasteToGridFromClipboard(context, (row, col, parsedValue) => { - emitCellValue(_cells[row.index].cells[col.index], parsedValue); + removeDataFromGrid(context, (cell) => { + emitCellValue(cell, undefined); }); - }, + } }, - { - code: 'ArrowRight', modifiers: ['Control', 'Shift'], handler: () => { - updateSelectionRange({ - leftTop: { col: selectedCellAddress.col, row: bounds.leftTop.row }, - rightBottom: { col: max.rightBottom.col, row: bounds.rightBottom.row }, - }); - }, + 'ctrl+c|meta+c': () => { + const context = createContext(); + copyGridDataToClipboard(data.value, context); }, - { - code: 'ArrowLeft', modifiers: ['Control', 'Shift'], handler: () => { - updateSelectionRange({ - leftTop: { col: max.leftTop.col, row: bounds.leftTop.row }, - rightBottom: { col: selectedCellAddress.col, row: bounds.rightBottom.row }, - }); - }, + 'ctrl+v|meta+v': async () => { + const _cells = cells.value; + const context = createContext(); + await pasteToGridFromClipboard(context, (row, col, parsedValue) => { + emitCellValue(_cells[row.index].cells[col.index], parsedValue); + }); }, - { - code: 'ArrowUp', modifiers: ['Control', 'Shift'], handler: () => { - updateSelectionRange({ - leftTop: { col: bounds.leftTop.col, row: max.leftTop.row }, - rightBottom: { col: bounds.rightBottom.col, row: selectedCellAddress.row }, - }); - }, + 'ctrl+shift+right|meta+shift+right': () => { + updateSelectionRange({ + leftTop: { col: selectedCellAddress.col, row: bounds.leftTop.row }, + rightBottom: { col: max.rightBottom.col, row: bounds.rightBottom.row }, + }); }, - { - code: 'ArrowDown', modifiers: ['Control', 'Shift'], handler: () => { - updateSelectionRange({ - leftTop: { col: bounds.leftTop.col, row: selectedCellAddress.row }, - rightBottom: { col: bounds.rightBottom.col, row: max.rightBottom.row }, - }); - }, + 'ctrl+shift+left|meta+shift+left': () => { + updateSelectionRange({ + leftTop: { col: max.leftTop.col, row: bounds.leftTop.row }, + rightBottom: { col: selectedCellAddress.col, row: bounds.rightBottom.row }, + }); }, - { - code: 'ArrowRight', modifiers: ['Shift'], handler: () => { - updateSelectionRange({ - leftTop: { - col: bounds.leftTop.col < selectedCellAddress.col - ? bounds.leftTop.col + 1 - : selectedCellAddress.col, - row: bounds.leftTop.row, - }, - rightBottom: { - col: (bounds.rightBottom.col > selectedCellAddress.col || bounds.leftTop.col === selectedCellAddress.col) - ? bounds.rightBottom.col + 1 - : selectedCellAddress.col, - row: bounds.rightBottom.row, - }, - }); - }, + 'ctrl+shift+up|meta+shift+up': () => { + updateSelectionRange({ + leftTop: { col: bounds.leftTop.col, row: max.leftTop.row }, + rightBottom: { col: bounds.rightBottom.col, row: selectedCellAddress.row }, + }); }, - { - code: 'ArrowLeft', modifiers: ['Shift'], handler: () => { - updateSelectionRange({ - leftTop: { - col: (bounds.leftTop.col < selectedCellAddress.col || bounds.rightBottom.col === selectedCellAddress.col) - ? bounds.leftTop.col - 1 - : selectedCellAddress.col, - row: bounds.leftTop.row, - }, - rightBottom: { - col: bounds.rightBottom.col > selectedCellAddress.col - ? bounds.rightBottom.col - 1 - : selectedCellAddress.col, - row: bounds.rightBottom.row, - }, - }); - }, + 'ctrl+shift+down|meta+shift+down': () => { + updateSelectionRange({ + leftTop: { col: bounds.leftTop.col, row: selectedCellAddress.row }, + rightBottom: { col: bounds.rightBottom.col, row: max.rightBottom.row }, + }); }, - { - code: 'ArrowUp', modifiers: ['Shift'], handler: () => { - updateSelectionRange({ - leftTop: { - col: bounds.leftTop.col, - row: (bounds.leftTop.row < selectedCellAddress.row || bounds.rightBottom.row === selectedCellAddress.row) - ? bounds.leftTop.row - 1 - : selectedCellAddress.row, - }, - rightBottom: { - col: bounds.rightBottom.col, - row: bounds.rightBottom.row > selectedCellAddress.row - ? bounds.rightBottom.row - 1 - : selectedCellAddress.row, - }, - }); - }, + 'ctrl+right|meta+right': () => { + selectionCell({ col: max.rightBottom.col, row: selectedCellAddress.row }); }, - { - code: 'ArrowDown', modifiers: ['Shift'], handler: () => { - updateSelectionRange({ - leftTop: { - col: bounds.leftTop.col, - row: bounds.leftTop.row < selectedCellAddress.row - ? bounds.leftTop.row + 1 - : selectedCellAddress.row, - }, - rightBottom: { - col: bounds.rightBottom.col, - row: (bounds.rightBottom.row > selectedCellAddress.row || bounds.leftTop.row === selectedCellAddress.row) - ? bounds.rightBottom.row + 1 - : selectedCellAddress.row, - }, - }); - }, + 'ctrl+left|meta+left': () => { + selectionCell({ col: max.leftTop.col, row: selectedCellAddress.row }); + }, + 'ctrl+up|meta+up': () => { + selectionCell({ col: selectedCellAddress.col, row: max.leftTop.row }); + }, + 'ctrl+down|meta+down': () => { + selectionCell({ col: selectedCellAddress.col, row: max.rightBottom.row }); + }, + 'shift+right': () => { + updateSelectionRange({ + leftTop: { + col: bounds.leftTop.col < selectedCellAddress.col + ? bounds.leftTop.col + 1 + : selectedCellAddress.col, + row: bounds.leftTop.row, + }, + rightBottom: { + col: (bounds.rightBottom.col > selectedCellAddress.col || bounds.leftTop.col === selectedCellAddress.col) + ? bounds.rightBottom.col + 1 + : selectedCellAddress.col, + row: bounds.rightBottom.row, + }, + }); + }, + 'shift+left': () => { + updateSelectionRange({ + leftTop: { + col: (bounds.leftTop.col < selectedCellAddress.col || bounds.rightBottom.col === selectedCellAddress.col) + ? bounds.leftTop.col - 1 + : selectedCellAddress.col, + row: bounds.leftTop.row, + }, + rightBottom: { + col: bounds.rightBottom.col > selectedCellAddress.col + ? bounds.rightBottom.col - 1 + : selectedCellAddress.col, + row: bounds.rightBottom.row, + }, + }); + }, + 'shift+up': () => { + updateSelectionRange({ + leftTop: { + col: bounds.leftTop.col, + row: (bounds.leftTop.row < selectedCellAddress.row || bounds.rightBottom.row === selectedCellAddress.row) + ? bounds.leftTop.row - 1 + : selectedCellAddress.row, + }, + rightBottom: { + col: bounds.rightBottom.col, + row: bounds.rightBottom.row > selectedCellAddress.row + ? bounds.rightBottom.row - 1 + : selectedCellAddress.row, + }, + }); + }, + 'shift+down': () => { + updateSelectionRange({ + leftTop: { + col: bounds.leftTop.col, + row: bounds.leftTop.row < selectedCellAddress.row + ? bounds.leftTop.row + 1 + : selectedCellAddress.row, + }, + rightBottom: { + col: bounds.rightBottom.col, + row: (bounds.rightBottom.row > selectedCellAddress.row || bounds.leftTop.row === selectedCellAddress.row) + ? bounds.rightBottom.row + 1 + : selectedCellAddress.row, + }, + }); }, - { - code: 'ArrowDown', handler: () => { - selectionCell({ col: selectedCellAddress.col, row: selectedCellAddress.row + 1 }); - }, + 'down': () => { + selectionCell({ col: selectedCellAddress.col, row: selectedCellAddress.row + 1 }); }, - { - code: 'ArrowUp', handler: () => { - selectionCell({ col: selectedCellAddress.col, row: selectedCellAddress.row - 1 }); - }, + 'up': () => { + selectionCell({ col: selectedCellAddress.col, row: selectedCellAddress.row - 1 }); }, - { - code: 'ArrowRight', handler: () => { - selectionCell({ col: selectedCellAddress.col + 1, row: selectedCellAddress.row }); - }, + 'right': () => { + selectionCell({ col: selectedCellAddress.col + 1, row: selectedCellAddress.row }); }, - { - code: 'ArrowLeft', handler: () => { - selectionCell({ col: selectedCellAddress.col - 1, row: selectedCellAddress.row }); - }, + 'left': () => { + selectionCell({ col: selectedCellAddress.col - 1, row: selectedCellAddress.row }); }, - ]); + }, [])(ev); break; } |