summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components
diff options
context:
space:
mode:
authormisskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com>2025-10-24 06:31:35 +0000
committerGitHub <noreply@github.com>2025-10-24 06:31:35 +0000
commitb4e16c83e2823f6348842c23c48de2caae52563a (patch)
treee4efaf0f2910491d039aab59d544a92836b77266 /packages/frontend/src/components
parentMerge pull request #16591 from misskey-dev/develop (diff)
parentRelease: 2025.10.1 (diff)
downloadmisskey-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')
-rw-r--r--packages/frontend/src/components/MkAnimBg.fragment.glsl111
-rw-r--r--packages/frontend/src/components/MkAnimBg.vertex.glsl15
-rw-r--r--packages/frontend/src/components/MkAnimBg.vue123
-rw-r--r--packages/frontend/src/components/MkImageEffectorDialog.vue19
-rw-r--r--packages/frontend/src/components/MkPoll.vue20
-rw-r--r--packages/frontend/src/components/MkPostForm.vue95
-rw-r--r--packages/frontend/src/components/MkReactionsViewer.reaction.vue6
-rw-r--r--packages/frontend/src/components/MkSuperMenu.vue4
-rw-r--r--packages/frontend/src/components/MkUploaderDialog.vue1
-rw-r--r--packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue24
-rw-r--r--packages/frontend/src/components/MkWatermarkEditorDialog.vue2
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.vue11
-rw-r--r--packages/frontend/src/components/grid/MkGrid.vue262
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;
}