mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-27 07:14:39 +02:00
Merge remote-tracking branch 'msky/develop' into copilot/add-user-mute-settings
This commit is contained in:
@@ -3,12 +3,11 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { existsSync, readFileSync, globSync } from 'node:fs';
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import { basename, dirname } from 'node:path/posix';
|
||||
import { GENERATOR, type State, generate } from 'astring';
|
||||
import type * as estree from 'estree';
|
||||
import glob from 'fast-glob';
|
||||
import { format } from 'prettier';
|
||||
|
||||
interface SatisfiesExpression extends estree.BaseExpression {
|
||||
@@ -439,38 +438,37 @@ function toStories(component: string): Promise<string> {
|
||||
|
||||
// glob('src/{components,pages,ui,widgets}/**/*.vue')
|
||||
(async () => {
|
||||
const globs = await Promise.all([
|
||||
glob('src/components/global/Mk*.vue'),
|
||||
glob('src/components/global/RouterView.vue'),
|
||||
glob('src/components/MkAbuseReportWindow.vue'),
|
||||
glob('src/components/MkAccountMoved.vue'),
|
||||
glob('src/components/MkAchievements.vue'),
|
||||
glob('src/components/MkAnalogClock.vue'),
|
||||
glob('src/components/MkAnimBg.vue'),
|
||||
glob('src/components/MkAnnouncementDialog.vue'),
|
||||
glob('src/components/MkAntennaEditor.vue'),
|
||||
glob('src/components/MkAntennaEditorDialog.vue'),
|
||||
glob('src/components/MkAsUi.vue'),
|
||||
glob('src/components/MkAutocomplete.vue'),
|
||||
glob('src/components/MkAvatars.vue'),
|
||||
glob('src/components/Mk[B-E]*.vue'),
|
||||
glob('src/components/MkFlashPreview.vue'),
|
||||
glob('src/components/MkGalleryPostPreview.vue'),
|
||||
glob('src/components/MkSignupServerRules.vue'),
|
||||
glob('src/components/MkUserSetupDialog.vue'),
|
||||
glob('src/components/MkUserSetupDialog.*.vue'),
|
||||
glob('src/components/MkImgPreviewDialog.vue'),
|
||||
glob('src/components/MkInstanceCardMini.vue'),
|
||||
glob('src/components/MkInviteCode.vue'),
|
||||
glob('src/components/MkTagItem.vue'),
|
||||
glob('src/components/MkRoleSelectDialog.vue'),
|
||||
glob('src/components/grid/MkGrid.vue'),
|
||||
glob('src/pages/admin/custom-emojis-manager2.vue'),
|
||||
glob('src/pages/admin/overview.ap-requests.vue'),
|
||||
glob('src/pages/user/home.vue'),
|
||||
glob('src/pages/search.vue'),
|
||||
]);
|
||||
const components = globs.flat();
|
||||
const components = [
|
||||
globSync('src/components/global/Mk*.vue'),
|
||||
globSync('src/components/global/RouterView.vue'),
|
||||
globSync('src/components/MkAbuseReportWindow.vue'),
|
||||
globSync('src/components/MkAccountMoved.vue'),
|
||||
globSync('src/components/MkAchievements.vue'),
|
||||
globSync('src/components/MkAnalogClock.vue'),
|
||||
globSync('src/components/MkAnimBg.vue'),
|
||||
globSync('src/components/MkAnnouncementDialog.vue'),
|
||||
globSync('src/components/MkAntennaEditor.vue'),
|
||||
globSync('src/components/MkAntennaEditorDialog.vue'),
|
||||
globSync('src/components/MkAsUi.vue'),
|
||||
globSync('src/components/MkAutocomplete.vue'),
|
||||
globSync('src/components/MkAvatars.vue'),
|
||||
globSync('src/components/Mk[B-E]*.vue'),
|
||||
globSync('src/components/MkFlashPreview.vue'),
|
||||
globSync('src/components/MkGalleryPostPreview.vue'),
|
||||
globSync('src/components/MkSignupServerRules.vue'),
|
||||
globSync('src/components/MkUserSetupDialog.vue'),
|
||||
globSync('src/components/MkUserSetupDialog.*.vue'),
|
||||
globSync('src/components/MkImgPreviewDialog.vue'),
|
||||
globSync('src/components/MkInstanceCardMini.vue'),
|
||||
globSync('src/components/MkInviteCode.vue'),
|
||||
globSync('src/components/MkTagItem.vue'),
|
||||
globSync('src/components/MkRoleSelectDialog.vue'),
|
||||
globSync('src/components/grid/MkGrid.vue'),
|
||||
globSync('src/pages/admin/custom-emojis-manager2.vue'),
|
||||
globSync('src/pages/admin/overview.ap-requests.vue'),
|
||||
globSync('src/pages/user/home.vue'),
|
||||
globSync('src/pages/search.vue'),
|
||||
].flat();
|
||||
await Promise.all(components.map(async (component) => {
|
||||
const stories = component.replace(/\.vue$/, '.stories.ts');
|
||||
await writeFile(stories, await toStories(component));
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
type PluginOption
|
||||
} from 'vite';
|
||||
import fs from 'node:fs';
|
||||
import { glob } from 'glob';
|
||||
import JSON5 from 'json5';
|
||||
import MagicString, { SourceMap } from 'magic-string';
|
||||
import path from 'node:path'
|
||||
@@ -724,7 +723,7 @@ export function pluginCreateSearchIndexVirtualModule(options: Options, asigner:
|
||||
|
||||
async load(id) {
|
||||
if (id == '\0' + allSearchIndexFile) {
|
||||
const files = await Promise.all(options.targetFilePaths.map(async (filePathPattern) => await glob(filePathPattern))).then(paths => paths.flat());
|
||||
const files = options.targetFilePaths.map((filePathPattern) => fs.globSync(filePathPattern)).flat();
|
||||
let generatedFile = '';
|
||||
let arrayElements = '';
|
||||
for (let file of files) {
|
||||
|
||||
@@ -19,21 +19,19 @@
|
||||
"@analytics/google-analytics": "1.1.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@github/webauthn-json": "2.1.1",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-rc2",
|
||||
"i18n": "workspace:*",
|
||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.3",
|
||||
"@rollup/pluginutils": "5.3.0",
|
||||
"@sentry/vue": "10.26.0",
|
||||
"@sentry/vue": "10.29.0",
|
||||
"@syuilo/aiscript": "1.2.0",
|
||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.2",
|
||||
"@vue/compiler-sfc": "3.5.24",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.16",
|
||||
"analytics": "0.8.19",
|
||||
"astring": "1.9.0",
|
||||
"broadcast-channel": "7.2.0",
|
||||
"buraha": "0.0.1",
|
||||
"canvas-confetti": "1.9.4",
|
||||
@@ -46,10 +44,9 @@
|
||||
"compare-versions": "6.1.1",
|
||||
"cropperjs": "2.1.0",
|
||||
"date-fns": "4.1.0",
|
||||
"estree-walker": "3.0.3",
|
||||
"eventemitter3": "5.0.1",
|
||||
"execa": "9.6.0",
|
||||
"exifreader": "4.32.0",
|
||||
"execa": "9.6.1",
|
||||
"exifreader": "4.33.1",
|
||||
"frontend-shared": "workspace:*",
|
||||
"icons-subsetter": "workspace:*",
|
||||
"idb-keyval": "6.2.2",
|
||||
@@ -57,9 +54,8 @@
|
||||
"ios-haptics": "0.1.4",
|
||||
"is-file-animated": "1.0.2",
|
||||
"json5": "2.2.3",
|
||||
"magic-string": "0.30.21",
|
||||
"matter-js": "0.20.0",
|
||||
"mediabunny": "1.25.1",
|
||||
"mediabunny": "1.25.7",
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
"misskey-js": "workspace:*",
|
||||
@@ -70,19 +66,15 @@
|
||||
"qr-scanner": "1.4.2",
|
||||
"rollup": "4.53.3",
|
||||
"sanitize-html": "2.17.0",
|
||||
"sass": "1.94.2",
|
||||
"shiki": "3.15.0",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"sass": "1.95.0",
|
||||
"shiki": "3.19.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.181.2",
|
||||
"throttle-debounce": "5.0.2",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.3",
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "7.2.4",
|
||||
"vue": "3.5.24",
|
||||
"vite": "7.2.7",
|
||||
"vue": "3.5.25",
|
||||
"vuedraggable": "next",
|
||||
"wanakana": "5.3.1"
|
||||
},
|
||||
@@ -90,7 +82,7 @@
|
||||
"@misskey-dev/summaly": "5.2.5",
|
||||
"@storybook/addon-essentials": "8.6.14",
|
||||
"@storybook/addon-interactions": "8.6.14",
|
||||
"@storybook/addon-links": "10.0.8",
|
||||
"@storybook/addon-links": "10.1.4",
|
||||
"@storybook/addon-mdx-gfm": "8.6.14",
|
||||
"@storybook/addon-storysource": "8.6.14",
|
||||
"@storybook/blocks": "8.6.14",
|
||||
@@ -98,58 +90,59 @@
|
||||
"@storybook/core-events": "8.6.14",
|
||||
"@storybook/manager-api": "8.6.14",
|
||||
"@storybook/preview-api": "8.6.14",
|
||||
"@storybook/react": "10.0.8",
|
||||
"@storybook/react-vite": "10.0.8",
|
||||
"@storybook/react": "10.1.4",
|
||||
"@storybook/react-vite": "10.1.4",
|
||||
"@storybook/test": "8.6.14",
|
||||
"@storybook/theming": "8.6.14",
|
||||
"@storybook/types": "8.6.14",
|
||||
"@storybook/vue3": "10.0.8",
|
||||
"@storybook/vue3-vite": "10.0.8",
|
||||
"@storybook/vue3": "10.1.4",
|
||||
"@storybook/vue3-vite": "10.1.4",
|
||||
"@tabler/icons-webfont": "3.35.0",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/canvas-confetti": "1.9.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/micromatch": "4.0.10",
|
||||
"@types/node": "24.10.1",
|
||||
"@types/node": "24.10.2",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.47.0",
|
||||
"@typescript-eslint/parser": "8.47.0",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"@vue/compiler-core": "3.5.24",
|
||||
"@vue/runtime-core": "3.5.24",
|
||||
"@typescript-eslint/eslint-plugin": "8.49.0",
|
||||
"@typescript-eslint/parser": "8.49.0",
|
||||
"@vitest/coverage-v8": "4.0.15",
|
||||
"@vue/compiler-core": "3.5.25",
|
||||
"acorn": "8.15.0",
|
||||
"astring": "1.9.0",
|
||||
"cross-env": "10.1.0",
|
||||
"cypress": "15.7.0",
|
||||
"cypress": "15.7.1",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.6.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"happy-dom": "20.0.10",
|
||||
"eslint-plugin-vue": "10.6.2",
|
||||
"estree-walker": "3.0.3",
|
||||
"happy-dom": "20.0.11",
|
||||
"intersection-observer": "0.12.2",
|
||||
"magic-string": "0.30.21",
|
||||
"micromatch": "4.0.8",
|
||||
"minimatch": "10.1.1",
|
||||
"msw": "2.12.2",
|
||||
"msw": "2.12.4",
|
||||
"msw-storybook-addon": "2.0.6",
|
||||
"nodemon": "3.1.11",
|
||||
"prettier": "3.6.2",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"prettier": "3.7.4",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "2.1.3",
|
||||
"storybook": "10.0.8",
|
||||
"storybook": "10.1.4",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"tsx": "4.20.6",
|
||||
"vite-plugin-glsl": "1.5.4",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "5.9.3",
|
||||
"vite-plugin-glsl": "1.5.5",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "4.0.13",
|
||||
"vitest": "4.0.15",
|
||||
"vitest-fetch-mock": "0.4.5",
|
||||
"vue-component-type-helpers": "3.1.5",
|
||||
"vue-component-type-helpers": "3.1.7",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.1.5"
|
||||
"vue-tsc": "3.1.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
console.error('invalid lang value detected!!!', typeof lang, lang);
|
||||
lang = 'en-US';
|
||||
}
|
||||
|
||||
localStorage.setItem('lang', lang);
|
||||
//#endregion
|
||||
|
||||
//#region Script
|
||||
|
||||
@@ -83,34 +83,58 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<MkButton v-if="foldersPaginator.canFetchOlder.value" primary rounded @click="foldersPaginator.fetchOlder()">{{ i18n.ts.loadMore }}</MkButton>
|
||||
|
||||
<MkStickyContainer v-for="(item, i) in filesTimeline" :key="`${item.date.getFullYear()}/${item.date.getMonth() + 1}`">
|
||||
<template #header>
|
||||
<div :class="$style.date">
|
||||
<span><i class="ti ti-chevron-down"></i> {{ item.date.getFullYear() }}/{{ item.date.getMonth() + 1 }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="shouldBeGroupedByDate">
|
||||
<MkStickyContainer v-for="(item, i) in filesTimeline" :key="`${item.date.getFullYear()}/${item.date.getMonth() + 1}`">
|
||||
<template #header>
|
||||
<div :class="$style.date">
|
||||
<span><i class="ti ti-chevron-down"></i> {{ item.date.getFullYear() }}/{{ item.date.getMonth() + 1 }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<TransitionGroup
|
||||
tag="div"
|
||||
:enterActiveClass="prefer.s.animation ? $style.transition_files_enterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_files_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_files_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_files_leaveTo : ''"
|
||||
:moveClass="prefer.s.animation ? $style.transition_files_move : ''"
|
||||
:class="$style.files"
|
||||
>
|
||||
<XFile
|
||||
v-for="file in item.items" :key="file.id"
|
||||
:class="$style.file"
|
||||
:file="file"
|
||||
:folder="folder"
|
||||
:isSelected="selectedFiles.some(x => x.id === file.id)"
|
||||
@click="onFileClick($event, file)"
|
||||
@dragstart="onFileDragstart(file, $event)"
|
||||
@dragend="isDragSource = false"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
<TransitionGroup
|
||||
v-else
|
||||
tag="div"
|
||||
:enterActiveClass="prefer.s.animation ? $style.transition_files_enterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_files_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_files_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_files_leaveTo : ''"
|
||||
:moveClass="prefer.s.animation ? $style.transition_files_move : ''"
|
||||
:class="$style.files"
|
||||
>
|
||||
<XFile
|
||||
v-for="file in filesPaginator.items.value" :key="file.id"
|
||||
:class="$style.file"
|
||||
:file="file"
|
||||
:folder="folder"
|
||||
:isSelected="selectedFiles.some(x => x.id === file.id)"
|
||||
@click="onFileClick($event, file)"
|
||||
@dragstart="onFileDragstart(file, $event)"
|
||||
@dragend="isDragSource = false"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
|
||||
<TransitionGroup
|
||||
tag="div"
|
||||
:enterActiveClass="prefer.s.animation ? $style.transition_files_enterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_files_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_files_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_files_leaveTo : ''"
|
||||
:moveClass="prefer.s.animation ? $style.transition_files_move : ''"
|
||||
:class="$style.files"
|
||||
>
|
||||
<XFile
|
||||
v-for="file in item.items" :key="file.id"
|
||||
:class="$style.file"
|
||||
:file="file"
|
||||
:folder="folder"
|
||||
:isSelected="selectedFiles.some(x => x.id === file.id)"
|
||||
@click="onFileClick($event, file)"
|
||||
@dragstart="onFileDragstart(file, $event)"
|
||||
@dragend="isDragSource = false"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
</MkStickyContainer>
|
||||
<MkButton v-show="filesPaginator.canFetchOlder.value" :class="$style.loadMore" primary rounded @click="filesPaginator.fetchOlder()">{{ i18n.ts.loadMore }}</MkButton>
|
||||
|
||||
<div v-if="filesPaginator.items.value.length == 0 && foldersPaginator.items.value.length == 0 && !fetching" :class="$style.empty">
|
||||
@@ -217,6 +241,7 @@ const foldersPaginator = markRaw(new Paginator('drive/folders', {
|
||||
}));
|
||||
|
||||
const filesTimeline = makeDateGroupedTimelineComputedRef(filesPaginator.items, 'month');
|
||||
const shouldBeGroupedByDate = computed(() => ['+createdAt', '-createdAt'].includes(sortModeSelect.value));
|
||||
|
||||
watch(folder, () => emit('cd', folder.value));
|
||||
watch(sortModeSelect, () => {
|
||||
|
||||
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import MkModalWindow from './MkModalWindow.vue';
|
||||
import type * as Misskey from 'misskey-js';
|
||||
|
||||
|
||||
@@ -233,16 +233,18 @@ function showMenu(ev: MouseEvent) {
|
||||
.hide {
|
||||
display: block;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
background-color: var(--MI_THEME-fg);
|
||||
color: hsl(from var(--MI_THEME-accent) h s calc(l + 10));
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||
backdrop-filter: var(--MI-blur, blur(15px));
|
||||
border-radius: 0 0 0 9px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
opacity: .5;
|
||||
padding: 5px 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.hiddenTextWrapper {
|
||||
@@ -272,17 +274,17 @@ html[data-color-scheme=light] .visible {
|
||||
.menu {
|
||||
display: block;
|
||||
position: absolute;
|
||||
border-radius: 999px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||
backdrop-filter: var(--MI-blur, blur(15px));
|
||||
border-radius: 9px 0 0 0;
|
||||
color: #fff;
|
||||
font-size: 0.8em;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
text-align: center;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!hardMuted && muted === false"
|
||||
v-if="!hardMuted && !hideByPlugin && muted === false"
|
||||
ref="rootEl"
|
||||
v-hotkey="keymap"
|
||||
:class="[$style.root, { [$style.showActionsOnlyHover]: prefer.s.showNoteActionsOnlyHover, [$style.skipRender]: prefer.s.skipNoteRender }]"
|
||||
@@ -38,7 +38,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
|
||||
<div v-if="isRenote && note.renote == null" :class="$style.deleted">
|
||||
{{ i18n.ts.deletedNote }}
|
||||
</div>
|
||||
<div v-else-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
|
||||
<MkAvatar :class="$style.collapsedRenoteTargetAvatar" :user="appearNote.user" link preview/>
|
||||
<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'respect'" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/>
|
||||
</div>
|
||||
@@ -158,7 +161,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false">
|
||||
<div v-else-if="!hardMuted && !hideByPlugin" :class="$style.muted" @click="muted = false">
|
||||
<I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small">
|
||||
<template #name>
|
||||
<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
|
||||
@@ -267,6 +270,7 @@ let note = deepClone(props.note);
|
||||
|
||||
// plugin
|
||||
const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
|
||||
const hideByPlugin = ref(false);
|
||||
if (noteViewInterruptors.length > 0) {
|
||||
let result: Misskey.entities.Note | null = deepClone(note);
|
||||
for (const interruptor of noteViewInterruptors) {
|
||||
@@ -276,7 +280,11 @@ if (noteViewInterruptors.length > 0) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
note = result as Misskey.entities.Note;
|
||||
if (result == null) {
|
||||
hideByPlugin.value = true;
|
||||
} else {
|
||||
note = result as Misskey.entities.Note;
|
||||
}
|
||||
}
|
||||
|
||||
const isRenote = Misskey.note.isPureRenote(note);
|
||||
@@ -1144,4 +1152,14 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||
opacity: .8;
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
.deleted {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
margin: 6px 32px 28px;
|
||||
--color: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.15));
|
||||
background-size: auto auto;
|
||||
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="!muted && !isDeleted"
|
||||
v-if="!muted && !hideByPlugin && !isDeleted"
|
||||
ref="rootEl"
|
||||
v-hotkey="keymap"
|
||||
:class="$style.root"
|
||||
@@ -43,178 +43,183 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<article :class="$style.note" @contextmenu.stop="onContextmenu">
|
||||
<header :class="$style.noteHeader">
|
||||
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
|
||||
<div :class="$style.noteHeaderBody">
|
||||
<div>
|
||||
<MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
|
||||
<MkUserName :nowrap="false" :user="appearNote.user"/>
|
||||
<div v-if="isRenote && note.renote == null" :class="$style.deleted">
|
||||
{{ i18n.ts.deletedNote }}
|
||||
</div>
|
||||
<template v-else>
|
||||
<article :class="$style.note" @contextmenu.stop="onContextmenu">
|
||||
<header :class="$style.noteHeader">
|
||||
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
|
||||
<div :class="$style.noteHeaderBody">
|
||||
<div>
|
||||
<MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
|
||||
<MkUserName :nowrap="false" :user="appearNote.user"/>
|
||||
</MkA>
|
||||
<span v-if="appearNote.user.isBot" :class="$style.isBot">bot</span>
|
||||
<div :class="$style.noteHeaderInfo">
|
||||
<span v-if="appearNote.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]">
|
||||
<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i>
|
||||
<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock"></i>
|
||||
<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
||||
</span>
|
||||
<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.noteHeaderUsernameAndBadgeRoles">
|
||||
<div :class="$style.noteHeaderUsername">
|
||||
<MkAcct :user="appearNote.user"/>
|
||||
</div>
|
||||
<div v-if="appearNote.user.badgeRoles" :class="$style.noteHeaderBadgeRoles">
|
||||
<img v-for="(role, i) in appearNote.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.noteHeaderBadgeRole" :src="role.iconUrl!"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
|
||||
</div>
|
||||
</header>
|
||||
<div :class="$style.noteContent">
|
||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||
<Mfm
|
||||
v-if="appearNote.cw != ''"
|
||||
:text="appearNote.cw"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
/>
|
||||
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/>
|
||||
</p>
|
||||
<div v-show="appearNote.cw == null || showContent">
|
||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||
<Mfm
|
||||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
class="_selectable"
|
||||
/>
|
||||
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
||||
<div v-if="translating || translation" :class="$style.translation">
|
||||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else-if="translation">
|
||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="appearNote.files && appearNote.files.length > 0">
|
||||
<MkMediaList ref="galleryEl" :mediaList="appearNote.files"/>
|
||||
</div>
|
||||
<MkPoll
|
||||
v-if="appearNote.poll"
|
||||
:noteId="appearNote.id"
|
||||
:multiple="appearNote.poll.multiple"
|
||||
:expiresAt="appearNote.poll.expiresAt"
|
||||
:choices="$appearNote.pollChoices"
|
||||
:author="appearNote.user"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:class="$style.poll"
|
||||
/>
|
||||
<div v-if="isEnabledUrlPreview">
|
||||
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
|
||||
</div>
|
||||
<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
|
||||
</div>
|
||||
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
||||
</div>
|
||||
<footer>
|
||||
<div :class="$style.noteFooterInfo">
|
||||
<MkA :to="notePage(appearNote)">
|
||||
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
|
||||
</MkA>
|
||||
<span v-if="appearNote.user.isBot" :class="$style.isBot">bot</span>
|
||||
<div :class="$style.noteHeaderInfo">
|
||||
<span v-if="appearNote.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]">
|
||||
<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i>
|
||||
<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock"></i>
|
||||
<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
||||
</span>
|
||||
<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.noteHeaderUsernameAndBadgeRoles">
|
||||
<div :class="$style.noteHeaderUsername">
|
||||
<MkAcct :user="appearNote.user"/>
|
||||
</div>
|
||||
<div v-if="appearNote.user.badgeRoles" :class="$style.noteHeaderBadgeRoles">
|
||||
<img v-for="(role, i) in appearNote.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.noteHeaderBadgeRole" :src="role.iconUrl!"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
|
||||
</div>
|
||||
</header>
|
||||
<div :class="$style.noteContent">
|
||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||
<Mfm
|
||||
v-if="appearNote.cw != ''"
|
||||
:text="appearNote.cw"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
/>
|
||||
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/>
|
||||
</p>
|
||||
<div v-show="appearNote.cw == null || showContent">
|
||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||
<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||
<Mfm
|
||||
v-if="appearNote.text"
|
||||
:parsedNodes="parsed"
|
||||
:text="appearNote.text"
|
||||
:author="appearNote.user"
|
||||
:nyaize="'respect'"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
class="_selectable"
|
||||
/>
|
||||
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
||||
<div v-if="translating || translation" :class="$style.translation">
|
||||
<MkLoading v-if="translating" mini/>
|
||||
<div v-else-if="translation">
|
||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="appearNote.files && appearNote.files.length > 0">
|
||||
<MkMediaList ref="galleryEl" :mediaList="appearNote.files"/>
|
||||
</div>
|
||||
<MkPoll
|
||||
v-if="appearNote.poll"
|
||||
<MkReactionsViewer
|
||||
v-if="appearNote.reactionAcceptance !== 'likeOnly'"
|
||||
style="margin-top: 6px;"
|
||||
:reactions="$appearNote.reactions"
|
||||
:reactionEmojis="$appearNote.reactionEmojis"
|
||||
:myReaction="$appearNote.myReaction"
|
||||
:noteId="appearNote.id"
|
||||
:multiple="appearNote.poll.multiple"
|
||||
:expiresAt="appearNote.poll.expiresAt"
|
||||
:choices="$appearNote.pollChoices"
|
||||
:author="appearNote.user"
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:class="$style.poll"
|
||||
:maxNumber="16"
|
||||
@mockUpdateMyReaction="emitUpdReaction"
|
||||
/>
|
||||
<div v-if="isEnabledUrlPreview">
|
||||
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
|
||||
</div>
|
||||
<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
|
||||
</div>
|
||||
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
||||
</div>
|
||||
<footer>
|
||||
<div :class="$style.noteFooterInfo">
|
||||
<MkA :to="notePage(appearNote)">
|
||||
<MkTime :time="appearNote.createdAt" mode="detail" colored/>
|
||||
</MkA>
|
||||
</div>
|
||||
<MkReactionsViewer
|
||||
v-if="appearNote.reactionAcceptance !== 'likeOnly'"
|
||||
style="margin-top: 6px;"
|
||||
:reactions="$appearNote.reactions"
|
||||
:reactionEmojis="$appearNote.reactionEmojis"
|
||||
:myReaction="$appearNote.myReaction"
|
||||
:noteId="appearNote.id"
|
||||
:maxNumber="16"
|
||||
@mockUpdateMyReaction="emitUpdReaction"
|
||||
/>
|
||||
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
|
||||
<i class="ti ti-arrow-back-up"></i>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
||||
</button>
|
||||
<button
|
||||
v-if="canRenote"
|
||||
ref="renoteButton"
|
||||
class="_button"
|
||||
:class="$style.noteFooterButton"
|
||||
@mousedown.prevent="renote()"
|
||||
>
|
||||
<i class="ti ti-repeat"></i>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.renoteCount) }}</p>
|
||||
</button>
|
||||
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
|
||||
<i class="ti ti-ban"></i>
|
||||
</button>
|
||||
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && $appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
|
||||
<i v-else-if="$appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
|
||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
|
||||
<i v-else class="ti ti-plus"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && $appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number($appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()">
|
||||
<i class="ti ti-paperclip"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="showMenu()">
|
||||
<i class="ti ti-dots"></i>
|
||||
</button>
|
||||
</footer>
|
||||
</article>
|
||||
<div :class="$style.tabs">
|
||||
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' }]" @click="tab = 'replies'"><i class="ti ti-arrow-back-up"></i> {{ i18n.ts.replies }}</button>
|
||||
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'renotes' }]" @click="tab = 'renotes'"><i class="ti ti-repeat"></i> {{ i18n.ts.renotes }}</button>
|
||||
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ti ti-icons"></i> {{ i18n.ts.reactions }}</button>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="tab === 'replies'">
|
||||
<div v-if="!repliesLoaded" style="padding: 16px">
|
||||
<MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton>
|
||||
</div>
|
||||
<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
|
||||
<MkPagination :paginator="renotesPaginator">
|
||||
<template #default="{ items }">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
||||
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
|
||||
<MkUserCardMini :user="item.user" :withChart="false"/>
|
||||
</MkA>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
|
||||
<div :class="$style.reactionTabs">
|
||||
<button v-for="reaction in Object.keys($appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = reaction">
|
||||
<MkReactionIcon :reaction="reaction"/>
|
||||
<span style="margin-left: 4px;">{{ $appearNote.reactions[reaction] }}</span>
|
||||
<button class="_button" :class="$style.noteFooterButton" @click="reply()">
|
||||
<i class="ti ti-arrow-back-up"></i>
|
||||
<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
||||
</button>
|
||||
</div>
|
||||
<MkPagination v-if="reactionTabType" :key="reactionTabType" :paginator="reactionsPaginator">
|
||||
<template #default="{ items }">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
||||
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
|
||||
<MkUserCardMini :user="item.user" :withChart="false"/>
|
||||
</MkA>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
<button
|
||||
v-if="canRenote"
|
||||
ref="renoteButton"
|
||||
class="_button"
|
||||
:class="$style.noteFooterButton"
|
||||
@mousedown.prevent="renote()"
|
||||
>
|
||||
<i class="ti ti-repeat"></i>
|
||||
<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.renoteCount) }}</p>
|
||||
</button>
|
||||
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
|
||||
<i class="ti ti-ban"></i>
|
||||
</button>
|
||||
<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
|
||||
<i v-if="appearNote.reactionAcceptance === 'likeOnly' && $appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
|
||||
<i v-else-if="$appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
|
||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
|
||||
<i v-else class="ti ti-plus"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && $appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number($appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()">
|
||||
<i class="ti ti-paperclip"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="showMenu()">
|
||||
<i class="ti ti-dots"></i>
|
||||
</button>
|
||||
</footer>
|
||||
</article>
|
||||
<div :class="$style.tabs">
|
||||
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'replies' }]" @click="tab = 'replies'"><i class="ti ti-arrow-back-up"></i> {{ i18n.ts.replies }}</button>
|
||||
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'renotes' }]" @click="tab = 'renotes'"><i class="ti ti-repeat"></i> {{ i18n.ts.renotes }}</button>
|
||||
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ti ti-icons"></i> {{ i18n.ts.reactions }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="tab === 'replies'">
|
||||
<div v-if="!repliesLoaded" style="padding: 16px">
|
||||
<MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton>
|
||||
</div>
|
||||
<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
|
||||
<MkPagination :paginator="renotesPaginator" :forceDisableInfiniteScroll="true">
|
||||
<template #default="{ items }">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
||||
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
|
||||
<MkUserCardMini :user="item.user" :withChart="false"/>
|
||||
</MkA>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
|
||||
<div :class="$style.reactionTabs">
|
||||
<button v-for="reaction in Object.keys($appearNote.reactions)" :key="reaction" :class="[$style.reactionTab, { [$style.reactionTabActive]: reactionTabType === reaction }]" class="_button" @click="reactionTabType = reaction">
|
||||
<MkReactionIcon :reaction="reaction"/>
|
||||
<span style="margin-left: 4px;">{{ $appearNote.reactions[reaction] }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<MkPagination v-if="reactionTabType" :key="reactionTabType" :paginator="reactionsPaginator" :forceDisableInfiniteScroll="true">
|
||||
<template #default="{ items }">
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); grid-gap: 12px;">
|
||||
<MkA v-for="item in items" :key="item.id" :to="userPage(item.user)">
|
||||
<MkUserCardMini :user="item.user" :withChart="false"/>
|
||||
</MkA>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="muted" class="_panel" :class="$style.muted" @click="muted = false">
|
||||
<I18n :src="i18n.ts.userSaysSomething" tag="small">
|
||||
@@ -289,6 +294,7 @@ let note = deepClone(props.note);
|
||||
|
||||
// plugin
|
||||
const noteViewInterruptors = getPluginHandlers('note_view_interruptor');
|
||||
const hideByPlugin = ref(false);
|
||||
if (noteViewInterruptors.length > 0) {
|
||||
let result: Misskey.entities.Note | null = deepClone(note);
|
||||
for (const interruptor of noteViewInterruptors) {
|
||||
@@ -298,7 +304,11 @@ if (noteViewInterruptors.length > 0) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
note = result as Misskey.entities.Note;
|
||||
if (result == null) {
|
||||
hideByPlugin.value = true;
|
||||
} else {
|
||||
note = result as Misskey.entities.Note;
|
||||
}
|
||||
}
|
||||
|
||||
const isRenote = Misskey.note.isPureRenote(note);
|
||||
@@ -944,4 +954,14 @@ function loadConversation() {
|
||||
text-align: center;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.deleted {
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
margin: 6px 32px 32px;
|
||||
--color: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.15));
|
||||
background-size: auto auto;
|
||||
background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template v-if="showDecoration">
|
||||
<img
|
||||
v-for="decoration in decorations ?? user.avatarDecorations"
|
||||
:class="[$style.decoration, { [$style.decorationBlink]: decoration.blink }]"
|
||||
:class="[$style.decoration, { [$style.decorationBlink]: getDecorationIsBrink(decoration) }]"
|
||||
:src="getDecorationUrl(decoration)"
|
||||
:style="{
|
||||
rotate: getDecorationAngle(decoration),
|
||||
@@ -56,13 +56,16 @@ import { prefer } from '@/preferences.js';
|
||||
const animation = ref(prefer.s.animation);
|
||||
const squareAvatars = ref(prefer.s.squareAvatars);
|
||||
|
||||
type Decoration = Misskey.entities.UserDetailed['avatarDecorations'][number];
|
||||
type DecorationEditorDecoration = Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'> & { blink?: boolean; };
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
user: Misskey.entities.User;
|
||||
target?: string | null;
|
||||
link?: boolean;
|
||||
preview?: boolean;
|
||||
indicator?: boolean;
|
||||
decorations?: (Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'> & { blink?: boolean; })[];
|
||||
decorations?: DecorationEditorDecoration[];
|
||||
forceShowDecoration?: boolean;
|
||||
}>(), {
|
||||
target: null,
|
||||
@@ -93,27 +96,31 @@ function onClick(ev: MouseEvent): void {
|
||||
emit('click', ev);
|
||||
}
|
||||
|
||||
function getDecorationUrl(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
|
||||
function getDecorationUrl(decoration: Decoration | DecorationEditorDecoration) {
|
||||
if (prefer.s.disableShowingAnimatedImages || prefer.s.dataSaver.avatar) return getStaticImageUrl(decoration.url);
|
||||
return decoration.url;
|
||||
}
|
||||
|
||||
function getDecorationAngle(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
|
||||
function getDecorationAngle(decoration: Decoration | DecorationEditorDecoration) {
|
||||
const angle = decoration.angle ?? 0;
|
||||
return angle === 0 ? undefined : `${angle * 360}deg`;
|
||||
}
|
||||
|
||||
function getDecorationScale(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
|
||||
function getDecorationScale(decoration: Decoration | DecorationEditorDecoration) {
|
||||
const scaleX = decoration.flipH ? -1 : 1;
|
||||
return scaleX === 1 ? undefined : `${scaleX} 1`;
|
||||
}
|
||||
|
||||
function getDecorationOffset(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
|
||||
function getDecorationOffset(decoration: Decoration | DecorationEditorDecoration) {
|
||||
const offsetX = decoration.offsetX ?? 0;
|
||||
const offsetY = decoration.offsetY ?? 0;
|
||||
return offsetX === 0 && offsetY === 0 ? undefined : `${offsetX * 100}% ${offsetY * 100}%`;
|
||||
}
|
||||
|
||||
function getDecorationIsBrink(decoration: Decoration | DecorationEditorDecoration) {
|
||||
return 'blink' in decoration && decoration.blink === true;
|
||||
}
|
||||
|
||||
const color = ref<string | undefined>();
|
||||
|
||||
watch(() => props.user.avatarBlurhash, () => {
|
||||
|
||||
@@ -74,6 +74,7 @@ function mount() {
|
||||
}
|
||||
|
||||
function back() {
|
||||
if (tabs.value.length <= 1) return; // transitionの関係でタブが1つの状態でbackが呼ばれることがある
|
||||
const prev = tabs.value[tabs.value.length - 2];
|
||||
tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)];
|
||||
router?.replaceByPath(prev.fullPath);
|
||||
|
||||
@@ -77,7 +77,7 @@ export const components = {
|
||||
SearchIcon: SearchIcon,
|
||||
};
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
I18n: typeof I18n;
|
||||
RouterView: typeof RouterView;
|
||||
|
||||
@@ -239,7 +239,7 @@ async function search() {
|
||||
}
|
||||
|
||||
const headerActions = computed(() => {
|
||||
if (channel.value && channel.value.userId) {
|
||||
if (channel.value) {
|
||||
const headerItems: PageHeaderItem[] = [];
|
||||
|
||||
headerItems.push({
|
||||
|
||||
@@ -193,7 +193,7 @@ function start() {
|
||||
}
|
||||
|
||||
function getIsLegacy(version: string | null): boolean {
|
||||
if (version == null) return false;
|
||||
if (version == null) return true;
|
||||
try {
|
||||
return compareVersions(version, '1.0.0') < 0;
|
||||
} catch {
|
||||
@@ -206,7 +206,7 @@ async function run() {
|
||||
if (!flash.value) return;
|
||||
|
||||
const version = utils.getLangVersion(flash.value.script);
|
||||
const isLegacy = version != null && getIsLegacy(version);
|
||||
const isLegacy = getIsLegacy(version);
|
||||
|
||||
const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript');
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ async function setAntenna() {
|
||||
})),
|
||||
} : undefined),
|
||||
],
|
||||
default: props.column.antennaId,
|
||||
default: antennas.find(x => x.id === props.column.antennaId)?.id,
|
||||
});
|
||||
|
||||
if (canceled || antennaIdOrOperation == null) return;
|
||||
|
||||
@@ -63,7 +63,7 @@ async function setChannel() {
|
||||
items: channels.map(x => ({
|
||||
value: x.id, label: x.name,
|
||||
})),
|
||||
default: props.column.channelId,
|
||||
default: channels.find(x => x.id === props.column.channelId)?.id,
|
||||
});
|
||||
if (canceled || chosenChannelId == null) return;
|
||||
const chosenChannel = channels.find(x => x.id === chosenChannelId)!;
|
||||
|
||||
@@ -70,7 +70,7 @@ async function setList() {
|
||||
})),
|
||||
} : undefined),
|
||||
],
|
||||
default: props.column.listId,
|
||||
default: lists.find(x => x.id === props.column.listId)?.id,
|
||||
});
|
||||
if (canceled || listIdOrOperation == null) return;
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ async function setRole() {
|
||||
items: roles.map(x => ({
|
||||
value: x.id, label: x.name,
|
||||
})),
|
||||
default: props.column.roleId,
|
||||
default: roles.find(x => x.id === props.column.roleId)?.id,
|
||||
});
|
||||
if (canceled || roleId == null) return;
|
||||
const role = roles.find(x => x.id === roleId)!;
|
||||
|
||||
@@ -104,6 +104,7 @@ async function setType() {
|
||||
}, {
|
||||
value: 'global', label: i18n.ts._timelines.global,
|
||||
}],
|
||||
default: props.column.tl,
|
||||
});
|
||||
if (canceled) {
|
||||
if (props.column.tl == null) {
|
||||
|
||||
Reference in New Issue
Block a user