Compare commits
19 Commits
2025.3.2-b
...
2025.3.2-b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
467c68fb98 | ||
![]() |
22b0ace8b4 | ||
![]() |
a814395127 | ||
![]() |
81a0cbd294 | ||
![]() |
32844e4775 | ||
![]() |
fbd9f47182 | ||
![]() |
1c9e25470a | ||
![]() |
ce6b2448ce | ||
![]() |
7d44b47fdf | ||
![]() |
dca42fd6e6 | ||
![]() |
43153311c6 | ||
![]() |
2b23c7e7f5 | ||
![]() |
2d4e85b466 | ||
![]() |
7bfada9792 | ||
![]() |
4fef9c670a | ||
![]() |
9599261fc3 | ||
![]() |
c2940fd77c | ||
![]() |
2ddedd0ce6 | ||
![]() |
c88f5f5195 |
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -24,9 +24,6 @@ updates:
|
||||
aws-sdk:
|
||||
patterns:
|
||||
- "@aws-sdk/*"
|
||||
bull-board:
|
||||
patterns:
|
||||
- "@bull-board/*"
|
||||
nestjs:
|
||||
patterns:
|
||||
- "@nestjs/*"
|
||||
|
@@ -1,18 +1,22 @@
|
||||
## 2025.3.2
|
||||
|
||||
### General
|
||||
-
|
||||
- セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
||||
- Misskeyネイティブでダッシュボードを実装予定です
|
||||
|
||||
### Client
|
||||
- Feat: 設定の管理が強化されました
|
||||
- 自動でバックアップされるように
|
||||
- 任意の設定項目をデバイス間で同期できるように(実験的)
|
||||
- 任意の設定項目をデバイス間で同期できるように
|
||||
- Enhance: プラグインの管理が強化されました
|
||||
- インストール/アンインストール/設定の変更時にリロード不要になりました
|
||||
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
|
||||
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
|
||||
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
|
||||
- Enhance: テーマ設定画面のデザインを改善
|
||||
- Enhance: 投稿フォームの設定メニューを改良
|
||||
- 投稿フォームをリセットできるように
|
||||
- 文字数カウントを復活
|
||||
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
||||
|
||||
### Server
|
||||
|
@@ -1190,7 +1190,7 @@ pastAnnouncements: "Informes passats"
|
||||
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
||||
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
||||
replies: "Respostes"
|
||||
renotes: "Impulsar"
|
||||
renotes: "Impulsos"
|
||||
loadReplies: "Mostrar les respostes"
|
||||
loadConversation: "Mostrar la conversació "
|
||||
pinnedList: "Llista fixada"
|
||||
@@ -1326,7 +1326,17 @@ restore: "Restaurar "
|
||||
syncBetweenDevices: "Sincronització entre dispositius"
|
||||
preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu"
|
||||
preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?"
|
||||
preferenceSyncConflictChoiceServer: "Valors de configuració del servidor"
|
||||
preferenceSyncConflictChoiceDevice: "Punts d'ajustos del dispositiu "
|
||||
preferenceSyncConflictChoiceCancel: "Cancel·lar l'activació de la sincronització "
|
||||
paste: "Pegar"
|
||||
emojiPalette: "Calaix d'emojis"
|
||||
postForm: "Formulari de publicació"
|
||||
_emojiPalette:
|
||||
palettes: "Calaixos d'emojis"
|
||||
enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius"
|
||||
paletteForMain: "Calaix d'emojis principal"
|
||||
paletteForReaction: "Calaix d'emojis per reaccions"
|
||||
_settings:
|
||||
driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius."
|
||||
pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se."
|
||||
@@ -1344,6 +1354,7 @@ _settings:
|
||||
preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències."
|
||||
appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer."
|
||||
soundsBanner: "Configuració dels sons que reproduirà el client."
|
||||
timelineAndNote: "Línia de temps i nota"
|
||||
_preferencesProfile:
|
||||
profileName: "Nom del perfil"
|
||||
profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
|
||||
@@ -2056,7 +2067,7 @@ _theme:
|
||||
hashtag: "Etiqueta"
|
||||
mention: "Menció"
|
||||
mentionMe: "Mencions (jo)"
|
||||
renote: "Renotar"
|
||||
renote: "Impulsar"
|
||||
modalBg: "Fons del modal"
|
||||
divider: "Divisor"
|
||||
scrollbarHandle: "Maneta de la barra de desplaçament"
|
||||
@@ -2498,7 +2509,7 @@ _notification:
|
||||
follow: "Segueix-me"
|
||||
mention: "Menció"
|
||||
reply: "Respostes"
|
||||
renote: "Impulsar"
|
||||
renote: "Impulsos"
|
||||
quote: "Citar"
|
||||
reaction: "Reaccions"
|
||||
pollEnded: "Enquesta terminada"
|
||||
@@ -2513,7 +2524,7 @@ _notification:
|
||||
_actions:
|
||||
followBack: "També et segueix"
|
||||
reply: "Respondre"
|
||||
renote: "Impulsos"
|
||||
renote: "Impulsar"
|
||||
_deck:
|
||||
alwaysShowMainColumn: "Mostrar sempre la columna principal"
|
||||
columnAlign: "Alinea les columnes"
|
||||
|
@@ -49,7 +49,7 @@ pin: "An dein Profil anheften"
|
||||
unpin: "Von deinem Profil lösen"
|
||||
copyContent: "Inhalt kopieren"
|
||||
copyLink: "Link kopieren"
|
||||
copyRemoteLink: "Renote-Link kopieren"
|
||||
copyRemoteLink: "Remote-Link kopieren"
|
||||
copyLinkRenote: "Renote-Link kopieren"
|
||||
delete: "Löschen"
|
||||
deleteAndEdit: "Löschen und Bearbeiten"
|
||||
|
20
locales/index.d.ts
vendored
20
locales/index.d.ts
vendored
@@ -2810,6 +2810,10 @@ export interface Locale extends ILocale {
|
||||
* コピー
|
||||
*/
|
||||
"copy": string;
|
||||
/**
|
||||
* クリップボードにコピーされました
|
||||
*/
|
||||
"copiedToClipboard": string;
|
||||
/**
|
||||
* メトリクス
|
||||
*/
|
||||
@@ -5346,6 +5350,10 @@ export interface Locale extends ILocale {
|
||||
* 投稿フォーム
|
||||
*/
|
||||
"postForm": string;
|
||||
/**
|
||||
* 文字数
|
||||
*/
|
||||
"textCount": string;
|
||||
"_emojiPalette": {
|
||||
/**
|
||||
* パレット
|
||||
@@ -5433,6 +5441,14 @@ export interface Locale extends ILocale {
|
||||
* タイムラインとノート
|
||||
*/
|
||||
"timelineAndNote": string;
|
||||
/**
|
||||
* 全てのテキスト要素を選択可能にする
|
||||
*/
|
||||
"makeEveryTextElementsSelectable": string;
|
||||
/**
|
||||
* 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
|
||||
*/
|
||||
"makeEveryTextElementsSelectable_description": string;
|
||||
};
|
||||
"_preferencesProfile": {
|
||||
/**
|
||||
@@ -9777,6 +9793,10 @@ export interface Locale extends ILocale {
|
||||
* ログイン
|
||||
*/
|
||||
"login": string;
|
||||
/**
|
||||
* アクセストークンの作成
|
||||
*/
|
||||
"createToken": string;
|
||||
/**
|
||||
* 通知のテスト
|
||||
*/
|
||||
|
@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name}が「{word}」について何かを言いまし
|
||||
makeActive: "アクティブにする"
|
||||
display: "表示"
|
||||
copy: "コピー"
|
||||
copiedToClipboard: "クリップボードにコピーされました"
|
||||
metrics: "メトリクス"
|
||||
overview: "概要"
|
||||
logs: "ログ"
|
||||
@@ -1332,6 +1333,7 @@ preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
|
||||
paste: "ペースト"
|
||||
emojiPalette: "絵文字パレット"
|
||||
postForm: "投稿フォーム"
|
||||
textCount: "文字数"
|
||||
|
||||
_emojiPalette:
|
||||
palettes: "パレット"
|
||||
@@ -1357,6 +1359,8 @@ _settings:
|
||||
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
|
||||
soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
|
||||
timelineAndNote: "タイムラインとノート"
|
||||
makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
|
||||
makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
|
||||
|
||||
_preferencesProfile:
|
||||
profileName: "プロファイル名"
|
||||
@@ -2584,6 +2588,7 @@ _notification:
|
||||
achievementEarned: "実績の獲得"
|
||||
exportCompleted: "エクスポートが完了した"
|
||||
login: "ログイン"
|
||||
createToken: "アクセストークンの作成"
|
||||
test: "通知のテスト"
|
||||
app: "連携アプリからの通知"
|
||||
|
||||
|
@@ -746,7 +746,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。
|
||||
public: "公开"
|
||||
private: "私密"
|
||||
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
|
||||
manageAccessTokens: "管理 Access Tokens"
|
||||
manageAccessTokens: "管理访问令牌"
|
||||
accountInfo: "账户信息"
|
||||
notesCount: "帖子数量"
|
||||
repliesCount: "回复数量"
|
||||
@@ -1339,13 +1339,24 @@ _emojiPalette:
|
||||
paletteForReaction: "回应用调色板"
|
||||
_settings:
|
||||
driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。"
|
||||
pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。"
|
||||
notificationsBanner: "可在此设置从服务器接收的通知的种类和范围,以及推送通知的设置。"
|
||||
api: "API"
|
||||
webhook: "Webhook"
|
||||
serviceConnection: "连接服务"
|
||||
serviceConnectionBanner: "可在此管理用于连接外部应用或服务的访问令牌及 Webhook。"
|
||||
accountData: "账户数据"
|
||||
accountDataBanner: "可在此导入或导出帐户数据的存档。"
|
||||
muteAndBlockBanner: "可在此设置隐藏内容,或限制指定用户能进行的操作。"
|
||||
accessibilityBanner: "可在此设置客户端的显示及动态效果等辅助设置。"
|
||||
privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。"
|
||||
securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。"
|
||||
preferencesBanner: "可在此设置客户端的整体运作行为。"
|
||||
appearanceBanner: "可在此设置客户端的外观及显示方式。"
|
||||
soundsBanner: "可在此设置客户端播放的声音。"
|
||||
timelineAndNote: "时间线和帖子"
|
||||
makeEveryTextElementsSelectable: "使所有的文字均可选择"
|
||||
makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。"
|
||||
_preferencesProfile:
|
||||
profileName: "配置名"
|
||||
profileNameDescription: "请指定用于识别此设备的名称"
|
||||
@@ -2510,6 +2521,7 @@ _notification:
|
||||
achievementEarned: "取得的成就"
|
||||
exportCompleted: "已完成导出"
|
||||
login: "登录"
|
||||
createToken: "创建访问令牌"
|
||||
test: "测试通知"
|
||||
app: "关联应用的通知"
|
||||
_actions:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.3.2-beta.1",
|
||||
"version": "2025.3.2-beta.3",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@@ -69,9 +69,6 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.749.0",
|
||||
"@aws-sdk/lib-storage": "3.749.0",
|
||||
"@bull-board/api": "6.7.7",
|
||||
"@bull-board/fastify": "6.7.7",
|
||||
"@bull-board/ui": "6.7.7",
|
||||
"@discordapp/twemoji": "15.1.0",
|
||||
"@fastify/accepts": "5.0.2",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
|
@@ -6,7 +6,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import cors from '@fastify/cors';
|
||||
import multipart from '@fastify/multipart';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||
import type { Config } from '@/config.js';
|
||||
@@ -57,8 +56,6 @@ export class ApiServerService {
|
||||
},
|
||||
});
|
||||
|
||||
fastify.register(fastifyCookie, {});
|
||||
|
||||
// Prevent cache
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
|
@@ -7,16 +7,12 @@ import { randomUUID } from 'node:crypto';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { createBullBoard } from '@bull-board/api';
|
||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
|
||||
import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify';
|
||||
import ms from 'ms';
|
||||
import sharp from 'sharp';
|
||||
import pug from 'pug';
|
||||
import { In, IsNull } from 'typeorm';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import fastifyView from '@fastify/view';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import fastifyProxy from '@fastify/http-proxy';
|
||||
import vary from 'vary';
|
||||
import htmlSafeJsonStringify from 'htmlescape';
|
||||
@@ -221,64 +217,6 @@ export class ClientServerService {
|
||||
|
||||
@bindThis
|
||||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
fastify.register(fastifyCookie, {});
|
||||
|
||||
//#region Bull Dashboard
|
||||
const bullBoardPath = '/queue';
|
||||
|
||||
// Authenticate
|
||||
fastify.addHook('onRequest', async (request, reply) => {
|
||||
if (request.routeOptions.url == null) {
|
||||
reply.code(404).send('Not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// %71ueueとかでリクエストされたら困るため
|
||||
const url = decodeURI(request.routeOptions.url);
|
||||
if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) {
|
||||
if (!url.startsWith(bullBoardPath + '/static/')) {
|
||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
}
|
||||
|
||||
const token = request.cookies.token;
|
||||
if (token == null) {
|
||||
reply.code(401).send('Login required');
|
||||
return;
|
||||
}
|
||||
const user = await this.usersRepository.findOneBy({ token });
|
||||
if (user == null) {
|
||||
reply.code(403).send('No such user');
|
||||
return;
|
||||
}
|
||||
const isAdministrator = await this.roleService.isAdministrator(user);
|
||||
if (!isAdministrator) {
|
||||
reply.code(403).send('Access denied');
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const bullBoardServerAdapter = new BullBoardFastifyAdapter();
|
||||
|
||||
createBullBoard({
|
||||
queues: [
|
||||
this.systemQueue,
|
||||
this.endedPollNotificationQueue,
|
||||
this.deliverQueue,
|
||||
this.inboxQueue,
|
||||
this.dbQueue,
|
||||
this.relationshipQueue,
|
||||
this.objectStorageQueue,
|
||||
this.userWebhookDeliverQueue,
|
||||
this.systemWebhookDeliverQueue,
|
||||
].map(q => new BullMQAdapter(q)),
|
||||
serverAdapter: bullBoardServerAdapter,
|
||||
});
|
||||
|
||||
bullBoardServerAdapter.setBasePath(bullBoardPath);
|
||||
(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
|
||||
//#endregion
|
||||
|
||||
fastify.register(fastifyView, {
|
||||
root: _dirname + '/views',
|
||||
engine: {
|
||||
|
@@ -6,7 +6,7 @@
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||
import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||
import type { SimpleGetResponse } from '../utils.js';
|
||||
import type * as misskey from 'misskey-js';
|
||||
|
||||
@@ -156,20 +156,20 @@ describe('Webリソース', () => {
|
||||
|
||||
describe(' has entry such ', () => {
|
||||
beforeEach(() => {
|
||||
post(alice, { text: "**a**" })
|
||||
post(alice, { text: '**a**' });
|
||||
});
|
||||
|
||||
test('MFMを含まない。', async () => {
|
||||
const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text());
|
||||
const content = await simpleGet(path(alice.username), '*/*', undefined, res => res.text());
|
||||
const _body: unknown = content.body;
|
||||
// JSONフィードのときは改めて文字列化する
|
||||
const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string;
|
||||
const body: string = typeof (_body) === 'object' ? JSON.stringify(_body) : _body as string;
|
||||
|
||||
if (body.includes("**a**")) {
|
||||
throw new Error("MFM shouldn't be included");
|
||||
if (body.includes('**a**')) {
|
||||
throw new Error('MFM shouldn\'t be included');
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
|
||||
@@ -180,24 +180,6 @@ describe('Webリソース', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
describe.each([{ path: '/queue' }])('$path', ({ path }) => {
|
||||
test('はログインしないとGETできない。', async () => await notOk({
|
||||
path,
|
||||
status: 401,
|
||||
}));
|
||||
|
||||
test('はadminでなければGETできない。', async () => await notOk({
|
||||
path,
|
||||
cookie: cookie(bob),
|
||||
status: 403,
|
||||
}));
|
||||
|
||||
test('はadminならGETできる。', async () => await ok({
|
||||
path,
|
||||
cookie: cookie(alice),
|
||||
}));
|
||||
});
|
||||
|
||||
describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
|
||||
test('はGETできない。', async () => await notOk({
|
||||
path,
|
||||
|
@@ -35,7 +35,7 @@ export type SystemWebhookPayload = {
|
||||
createdAt: string;
|
||||
type: string;
|
||||
body: any;
|
||||
}
|
||||
};
|
||||
|
||||
const config = loadConfig();
|
||||
export const port = config.port;
|
||||
@@ -45,10 +45,6 @@ export const host = new URL(config.url).host;
|
||||
export const WEBHOOK_HOST = 'http://localhost:15080';
|
||||
export const WEBHOOK_PORT = 15080;
|
||||
|
||||
export const cookie = (me: UserToken): string => {
|
||||
return `token=${me.token};`;
|
||||
};
|
||||
|
||||
export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = {
|
||||
endpoint: E,
|
||||
parameters: P,
|
||||
|
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { inject, watch, ref } from 'vue';
|
||||
import { watch, ref } from 'vue';
|
||||
import XReaction from '@/components/EmReactionsViewer.reaction.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
@@ -22,12 +22,6 @@ const props = withDefaults(defineProps<{
|
||||
maxNumber: Infinity,
|
||||
});
|
||||
|
||||
const mock = inject<boolean>('mock', false);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
|
||||
}>();
|
||||
|
||||
const initialReactions = new Set(Object.keys(props.note.reactions));
|
||||
|
||||
const reactions = ref<[string, number][]>([]);
|
||||
@@ -38,12 +32,8 @@ if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.m
|
||||
}
|
||||
|
||||
function onMockToggleReaction(emoji: string, count: number) {
|
||||
if (!mock) return;
|
||||
|
||||
const i = reactions.value.findIndex((item) => item[0] === emoji);
|
||||
if (i < 0) return;
|
||||
|
||||
emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1]));
|
||||
}
|
||||
|
||||
watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => {
|
||||
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<div>
|
||||
<div class="_fullinfo">
|
||||
<img :src="notFoundImageUrl" class="_ghost"/>
|
||||
<img :src="notFoundImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.notFoundDescription }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,5 +20,5 @@ import { i18n } from '@/i18n.js';
|
||||
|
||||
const serverMetadata = inject(DI.serverMetadata)!;
|
||||
|
||||
const notFoundImageUrl = computed(() => serverMetadata?.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
|
||||
const notFoundImageUrl = computed(() => serverMetadata.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
|
||||
</script>
|
||||
|
@@ -234,6 +234,10 @@ export async function common(createVue: () => App<Element>) {
|
||||
});
|
||||
}
|
||||
|
||||
if (prefer.s.makeEveryTextElementsSelectable) {
|
||||
document.documentElement.classList.add('forceSelectableAll');
|
||||
}
|
||||
|
||||
//#region Fetch user
|
||||
if ($i && $i.token) {
|
||||
if (_DEV_) {
|
||||
|
@@ -135,7 +135,7 @@ function forward() {
|
||||
|
||||
function showMenu(ev: MouseEvent) {
|
||||
os.popupMenu([{
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: 'Copy ID',
|
||||
action: () => {
|
||||
copyToClipboard(props.report.id);
|
||||
|
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.notFound }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
|
||||
|
@@ -48,7 +48,6 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
|
||||
|
||||
function copy() {
|
||||
copyToClipboard(props.code);
|
||||
os.success();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i>
|
||||
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
|
||||
</div>
|
||||
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
|
||||
<div v-if="text" :class="$style.text"><Mfm :text="text"/></div>
|
||||
<header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header>
|
||||
<div v-if="text" :class="$style.text" class="_selectable"><Mfm :text="text"/></div>
|
||||
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
|
||||
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
||||
<template #caption>
|
||||
|
@@ -297,7 +297,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||
}];
|
||||
if (prefer.s.devMode) {
|
||||
menu = menu.concat([{ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFolderId,
|
||||
action: () => {
|
||||
copyToClipboard(props.folder.id);
|
||||
|
@@ -194,7 +194,6 @@ function generate() {
|
||||
|
||||
function doCopy() {
|
||||
copyToClipboard(result.value);
|
||||
os.success();
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
|
@@ -14,8 +14,34 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined"
|
||||
:leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined"
|
||||
>
|
||||
<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/>
|
||||
<img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/>
|
||||
<canvas
|
||||
v-show="hide"
|
||||
key="canvas"
|
||||
ref="canvas"
|
||||
:class="$style.canvas"
|
||||
:width="canvasWidth"
|
||||
:height="canvasHeight"
|
||||
:title="title ?? undefined"
|
||||
draggable="false"
|
||||
tabindex="-1"
|
||||
style="-webkit-user-drag: none;"
|
||||
/>
|
||||
<img
|
||||
v-show="!hide"
|
||||
key="img"
|
||||
ref="img"
|
||||
:height="imgHeight ?? undefined"
|
||||
:width="imgWidth ?? undefined"
|
||||
:class="$style.img"
|
||||
:src="src ?? undefined"
|
||||
:title="title ?? undefined"
|
||||
:alt="alt ?? undefined"
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
draggable="false"
|
||||
tabindex="-1"
|
||||
style="-webkit-user-drag: none;"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[$style.root, { [$style.warn]: warn }]">
|
||||
<div :class="[$style.root, { [$style.warn]: warn }]" class="_selectable">
|
||||
<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i>
|
||||
<i v-else class="ti ti-info-circle" :class="$style.i"></i>
|
||||
<div><slot></slot></div>
|
||||
|
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="_selectable">
|
||||
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
|
||||
<div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]">
|
||||
<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
|
||||
@@ -45,13 +45,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
|
||||
import type { InputHTMLAttributes } from 'vue';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import type { InputHTMLAttributes } from 'vue';
|
||||
import type { SuggestionType } from '@/utility/autocomplete.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { Autocomplete } from '@/utility/autocomplete.js';
|
||||
import type { SuggestionType } from '@/utility/autocomplete.js';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | number | null;
|
||||
|
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div :class="$style.items">
|
||||
<div>
|
||||
<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
|
||||
<div>{{ invite.code }}</div>
|
||||
<div class="_selectableAtomic">{{ invite.code }}</div>
|
||||
</div>
|
||||
<div v-if="moderator">
|
||||
<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
|
||||
@@ -90,7 +90,6 @@ function deleteCode() {
|
||||
|
||||
function copyInviteCode() {
|
||||
copyToClipboard(props.invite.code);
|
||||
os.success();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div :class="$style.key">
|
||||
<slot name="key"></slot>
|
||||
</div>
|
||||
<div :class="$style.value">
|
||||
<div :class="$style.value" class="_selectable">
|
||||
<slot name="value"></slot>
|
||||
<button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button>
|
||||
</div>
|
||||
@@ -31,7 +31,6 @@ const props = withDefaults(defineProps<{
|
||||
|
||||
const copy_ = () => {
|
||||
copyToClipboard(props.copy);
|
||||
os.success();
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@@ -242,7 +242,7 @@ function showMenu(ev: MouseEvent) {
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menu.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
copyToClipboard(props.audio.id);
|
||||
|
@@ -168,7 +168,7 @@ function showMenu(ev: MouseEvent) {
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
copyToClipboard(props.image.id);
|
||||
|
@@ -267,7 +267,7 @@ function showMenu(ev: MouseEvent) {
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menu.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
copyToClipboard(props.video.id);
|
||||
|
@@ -35,6 +35,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
|
||||
<span><MkEllipsis/></span>
|
||||
</span>
|
||||
<div v-else-if="item.type === 'component'" role="menuitem" tabindex="-1" :class="[$style.componentItem]">
|
||||
<component :is="item.component" v-bind="item.props"/>
|
||||
</div>
|
||||
<MkA
|
||||
v-else-if="item.type === 'link'"
|
||||
role="menuitem"
|
||||
|
@@ -76,12 +76,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:emojiUrls="appearNote.emojis"
|
||||
:enableEmojiMenu="true"
|
||||
:enableEmojiMenuReaction="true"
|
||||
class="_selectable"
|
||||
/>
|
||||
<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"/>
|
||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -223,6 +224,7 @@ import { focusPrev, focusNext } from '@/utility/focus.js';
|
||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { getPluginHandlers } from '@/plugin.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
@@ -233,7 +235,7 @@ const props = withDefaults(defineProps<{
|
||||
mock: false,
|
||||
});
|
||||
|
||||
provide('mock', props.mock);
|
||||
provide(DI.mock, props.mock);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'reaction', emoji: string): void;
|
||||
|
@@ -97,13 +97,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
: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"/>
|
||||
<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">
|
||||
|
@@ -40,12 +40,13 @@ import * as Misskey from 'misskey-js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { notePage } from '@/filters/note.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
}>();
|
||||
|
||||
const mock = inject<boolean>('mock', false);
|
||||
const mock = inject(DI.mock, false);
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noNotes }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,10 +33,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { shallowRef } from 'vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import MkNote from '@/components/MkNote.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noNotifications }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div v-else-if="empty" key="_empty_" class="empty">
|
||||
<slot name="empty">
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</slot>
|
||||
|
95
packages/frontend/src/components/MkPostForm.TextCounter.vue
Normal file
95
packages/frontend/src/components/MkPostForm.TextCounter.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[$style.textCountRoot]">
|
||||
<div :class="$style.textCountLabel">{{ i18n.ts.textCount }}</div>
|
||||
<div
|
||||
:class="[$style.textCount,
|
||||
{ [$style.danger]: textCountPercentage > 100 },
|
||||
{ [$style.warning]: textCountPercentage > 90 && textCountPercentage <= 100 },
|
||||
]"
|
||||
>
|
||||
<div :class="$style.textCountGraph"></div>
|
||||
<div><span :class="$style.textCountCurrent">{{ number(textLength) }}</span> / {{ number(maxTextLength) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
import { instance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import number from '@/filters/number.js';
|
||||
|
||||
const props = defineProps<{
|
||||
textLength: number;
|
||||
}>();
|
||||
|
||||
const maxTextLength = computed(() => {
|
||||
return instance ? instance.maxNoteTextLength : 1000;
|
||||
});
|
||||
|
||||
const textCountPercentage = computed(() => {
|
||||
return props.textLength / maxTextLength.value * 100;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.textCountRoot {
|
||||
padding: 4px 14px;
|
||||
}
|
||||
|
||||
.textCountLabel {
|
||||
font-size: 11px;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.textCount {
|
||||
display: flex;
|
||||
gap: var(--MI-marginHalf);
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
--countColor: var(--MI_THEME-accent);
|
||||
|
||||
&.danger {
|
||||
--countColor: var(--MI_THEME-error);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
--countColor: var(--MI_THEME-warn);
|
||||
}
|
||||
|
||||
.textCountGraph {
|
||||
position: relative;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-image: conic-gradient(
|
||||
var(--countColor) 0% v-bind("Math.min(100, textCountPercentage) + '%'"),
|
||||
rgba(0, 0, 0, .2) v-bind("Math.min(100, textCountPercentage) + '%'") 100%
|
||||
);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--MI_THEME-popup);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.textCountCurrent {
|
||||
color: var(--countColor);
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<div :class="$style.headerRight">
|
||||
<template v-if="!(channel != null && fixed)">
|
||||
<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility">
|
||||
<button v-if="channel == null" ref="visibilityButton" v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility">
|
||||
<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span>
|
||||
<span v-if="visibility === 'home'"><i class="ti ti-home"></i></span>
|
||||
<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span>
|
||||
@@ -32,15 +32,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
|
||||
</button>
|
||||
</template>
|
||||
<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
|
||||
<button v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
|
||||
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
|
||||
<span v-else><i class="ti ti-rocket-off"></i></span>
|
||||
</button>
|
||||
<button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance">
|
||||
<span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span>
|
||||
<span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span>
|
||||
<span v-else><i class="ti ti-icons"></i></span>
|
||||
</button>
|
||||
<button ref="otherSettingsButton" v-tooltip="i18n.ts.other" class="_button" :class="$style.headerRightItem" @click="showOtherSettings"><i class="ti ti-dots"></i></button>
|
||||
<button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
|
||||
<div :class="$style.submitInner">
|
||||
<template v-if="posted"></template>
|
||||
@@ -111,9 +107,11 @@ import { toASCII } from 'punycode.js';
|
||||
import { host, url } from '@@/js/config.js';
|
||||
import type { ShallowRef } from 'vue';
|
||||
import type { PostFormProps } from '@/types/post-form.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
|
||||
import MkNotePreview from '@/components/MkNotePreview.vue';
|
||||
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
||||
import XTextCounter from '@/components/MkPostForm.TextCounter.vue';
|
||||
import MkPollEditor from '@/components/MkPollEditor.vue';
|
||||
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
||||
import { erase, unique } from '@/utility/array.js';
|
||||
@@ -138,6 +136,7 @@ import { emojiPicker } from '@/utility/emoji-picker.js';
|
||||
import { mfmFunctionPicker } from '@/utility/mfm-function-picker.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { getPluginHandlers } from '@/plugin.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
const $i = signinRequired();
|
||||
|
||||
@@ -155,7 +154,7 @@ const props = withDefaults(defineProps<PostFormProps & {
|
||||
initialLocalOnly: undefined,
|
||||
});
|
||||
|
||||
provide('mock', props.mock);
|
||||
provide(DI.mock, props.mock);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'posted'): void;
|
||||
@@ -170,6 +169,7 @@ const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
|
||||
const cwInputEl = shallowRef<HTMLInputElement | null>(null);
|
||||
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
|
||||
const visibilityButton = shallowRef<HTMLElement>();
|
||||
const otherSettingsButton = shallowRef<HTMLElement>();
|
||||
|
||||
const posting = ref(false);
|
||||
const posted = ref(false);
|
||||
@@ -555,6 +555,47 @@ async function toggleReactionAcceptance() {
|
||||
reactionAcceptance.value = select.result;
|
||||
}
|
||||
|
||||
//#region その他の設定メニューpopup
|
||||
function showOtherSettings() {
|
||||
let reactionAcceptanceIcon = 'ti ti-icons';
|
||||
|
||||
if (reactionAcceptance.value === 'likeOnly') {
|
||||
reactionAcceptanceIcon = 'ti ti-heart _love';
|
||||
} else if (reactionAcceptance.value === 'likeOnlyForRemote') {
|
||||
reactionAcceptanceIcon = 'ti ti-heart-plus';
|
||||
}
|
||||
|
||||
const menuItems = [{
|
||||
type: 'component',
|
||||
component: XTextCounter,
|
||||
props: {
|
||||
textLength: textLength,
|
||||
},
|
||||
}, { type: 'divider' }, {
|
||||
icon: reactionAcceptanceIcon,
|
||||
text: i18n.ts.reactionAcceptance,
|
||||
action: () => {
|
||||
toggleReactionAcceptance();
|
||||
},
|
||||
}, { type: 'divider' }, {
|
||||
icon: 'ti ti-trash',
|
||||
text: i18n.ts.reset,
|
||||
danger: true,
|
||||
action: async () => {
|
||||
if (props.mock) return;
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'question',
|
||||
text: i18n.ts.resetAreYouSure,
|
||||
});
|
||||
if (canceled) return;
|
||||
clear();
|
||||
},
|
||||
}] satisfies MenuItem[];
|
||||
|
||||
os.popupMenu(menuItems, otherSettingsButton.value);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
function pushVisibleUser(user: Misskey.entities.UserDetailed) {
|
||||
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
|
||||
visibleUsers.value.push(user);
|
||||
|
@@ -42,6 +42,7 @@ import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
|
||||
|
||||
@@ -50,7 +51,7 @@ const props = defineProps<{
|
||||
detachMediaFn?: (id: string) => void;
|
||||
}>();
|
||||
|
||||
const mock = inject<boolean>('mock', false);
|
||||
const mock = inject(DI.mock, false);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void;
|
||||
@@ -200,7 +201,7 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
copyToClipboard(file.id);
|
||||
|
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
@click="toggleReaction()"
|
||||
@contextmenu.prevent.stop="menu"
|
||||
>
|
||||
<MkReactionIcon :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
||||
<MkReactionIcon style="pointer-events: none;" :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
||||
<span :class="$style.count">{{ count }}</span>
|
||||
</button>
|
||||
</template>
|
||||
@@ -35,6 +35,7 @@ import * as sound from '@/utility/sound.js';
|
||||
import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js';
|
||||
import { customEmojisMap } from '@/custom-emojis.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
const props = defineProps<{
|
||||
reaction: string;
|
||||
@@ -43,7 +44,7 @@ const props = defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
}>();
|
||||
|
||||
const mock = inject<boolean>('mock', false);
|
||||
const mock = inject(DI.mock, false);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'reactionToggled', emoji: string, newCount: number): void;
|
||||
|
@@ -22,6 +22,7 @@ import * as Misskey from 'misskey-js';
|
||||
import { inject, watch, ref } from 'vue';
|
||||
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
@@ -30,7 +31,7 @@ const props = withDefaults(defineProps<{
|
||||
maxNumber: Infinity,
|
||||
});
|
||||
|
||||
const mock = inject<boolean>('mock', false);
|
||||
const mock = inject(DI.mock, false);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
|
||||
|
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="_selectable">
|
||||
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
|
||||
<div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;">
|
||||
<textarea
|
||||
@@ -38,10 +38,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
import type { SuggestionType } from '@/utility/autocomplete.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { Autocomplete } from '@/utility/autocomplete.js';
|
||||
import type { SuggestionType } from '@/utility/autocomplete.js';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | null;
|
||||
|
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -21,9 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import MkUserInfo from '@/components/MkUserInfo.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
|
||||
|
@@ -34,6 +34,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
translate: getDecorationOffset(decoration),
|
||||
}"
|
||||
alt=""
|
||||
draggable="false"
|
||||
style="-webkit-user-drag: none;"
|
||||
>
|
||||
</template>
|
||||
</component>
|
||||
|
@@ -9,6 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
||||
src="/client-assets/dummy.png"
|
||||
:title="alt"
|
||||
draggable="false"
|
||||
style="-webkit-user-drag: none;"
|
||||
/>
|
||||
<span v-else-if="errored">:{{ customEmojiName }}:</span>
|
||||
<img
|
||||
@@ -18,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:alt="alt"
|
||||
:title="alt"
|
||||
decoding="async"
|
||||
draggable="false"
|
||||
@error="errored = true"
|
||||
@load="errored = false"
|
||||
@click="onClick"
|
||||
@@ -97,7 +100,6 @@ function onClick(ev: MouseEvent) {
|
||||
icon: 'ti ti-copy',
|
||||
action: () => {
|
||||
copyToClipboard(`:${props.name}:`);
|
||||
os.success();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -157,6 +159,7 @@ async function edit(name: string) {
|
||||
.root {
|
||||
height: 2em;
|
||||
vertical-align: middle;
|
||||
-webkit-user-drag: none;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
|
@@ -50,7 +50,6 @@ function onClick(ev: MouseEvent) {
|
||||
icon: 'ti ti-copy',
|
||||
action: () => {
|
||||
copyToClipboard(props.emoji);
|
||||
os.success();
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
|
||||
<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p>
|
||||
<MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton>
|
||||
</div>
|
||||
|
@@ -1,32 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[$style.spacer, store.r.darkMode.value ? $style.dark : $style.light]"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { store } from '@/store.js';
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.spacer {
|
||||
box-sizing: border-box;
|
||||
padding: 32px;
|
||||
margin: 0 auto;
|
||||
height: 300px;
|
||||
background-clip: content-box;
|
||||
background-size: auto auto;
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
|
||||
&.light {
|
||||
background-image: repeating-linear-gradient(135deg, transparent, transparent 16px, #00000010 16px, #00000010 20px );
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background-image: repeating-linear-gradient(135deg, transparent, transparent 16px, #FFFFFF16 16px, #FFFFFF16 20px );
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -21,7 +21,6 @@ import MkError from './global/MkError.vue';
|
||||
import MkAd from './global/MkAd.vue';
|
||||
import MkPageHeader from './global/MkPageHeader.vue';
|
||||
import MkSpacer from './global/MkSpacer.vue';
|
||||
import MkFooterSpacer from './global/MkFooterSpacer.vue';
|
||||
import MkStickyContainer from './global/MkStickyContainer.vue';
|
||||
import MkLazy from './global/MkLazy.vue';
|
||||
import SearchMarker from './global/SearchMarker.vue';
|
||||
@@ -55,7 +54,6 @@ export const components = {
|
||||
MkAd: MkAd,
|
||||
MkPageHeader: MkPageHeader,
|
||||
MkSpacer: MkSpacer,
|
||||
MkFooterSpacer: MkFooterSpacer,
|
||||
MkStickyContainer: MkStickyContainer,
|
||||
MkLazy: MkLazy,
|
||||
SearchMarker: SearchMarker,
|
||||
@@ -83,7 +81,6 @@ declare module '@vue/runtime-core' {
|
||||
MkAd: typeof MkAd;
|
||||
MkPageHeader: typeof MkPageHeader;
|
||||
MkSpacer: typeof MkSpacer;
|
||||
MkFooterSpacer: typeof MkFooterSpacer;
|
||||
MkStickyContainer: typeof MkStickyContainer;
|
||||
MkLazy: typeof MkLazy;
|
||||
SearchMarker: typeof SearchMarker;
|
||||
|
@@ -9,4 +9,5 @@ import type { IRouter } from '@/nirax.js';
|
||||
export const DI = {
|
||||
routerCurrentDepth: Symbol() as InjectionKey<number>,
|
||||
router: Symbol() as InjectionKey<IRouter>,
|
||||
mock: Symbol() as InjectionKey<boolean>,
|
||||
};
|
||||
|
@@ -5,4 +5,4 @@
|
||||
|
||||
import { numberFormat } from '@@/js/intl-const.js';
|
||||
|
||||
export default n => n == null ? 'N/A' : numberFormat.format(n);
|
||||
export default (n?: number) => n == null ? 'N/A' : numberFormat.format(n);
|
||||
|
@@ -63,7 +63,6 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Miss
|
||||
});
|
||||
if (result === 'copy') {
|
||||
copyToClipboard(`Endpoint: ${endpoint}\nInfo: ${JSON.stringify(err.info)}\nDate: ${date}`);
|
||||
success();
|
||||
}
|
||||
return;
|
||||
} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
|
||||
|
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkLoading v-if="!loaded"/>
|
||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
|
||||
<div v-show="loaded" :class="$style.root">
|
||||
<img :src="serverErrorImageUrl" class="_ghost" :class="$style.img"/>
|
||||
<img :src="serverErrorImageUrl" draggable="false" :class="$style.img"/>
|
||||
<div class="_gaps">
|
||||
<div><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></div>
|
||||
<div v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</div>
|
||||
|
@@ -17,11 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import * as config from '@@/js/config.js';
|
||||
import XQueue from './queue.chart.vue';
|
||||
import XHeader from './_header_.vue';
|
||||
import type { Ref } from 'vue';
|
||||
import * as os from '@/os.js';
|
||||
import * as config from '@@/js/config.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
@@ -54,14 +54,7 @@ function promoteAllQueues() {
|
||||
});
|
||||
}
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
asFullButton: true,
|
||||
icon: 'ti ti-external-link',
|
||||
text: i18n.ts.dashboard,
|
||||
handler: () => {
|
||||
window.open(config.url + '/queue', '_blank', 'noopener');
|
||||
},
|
||||
}]);
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => [{
|
||||
key: 'deliver',
|
||||
|
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination :pagination="usersPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -210,7 +210,6 @@ const headerActions = computed(() => {
|
||||
return;
|
||||
}
|
||||
copyToClipboard(`${url}/channels/${channel.value.id}`);
|
||||
os.success();
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -148,7 +148,6 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
|
||||
text: i18n.ts.copyUrl,
|
||||
action: () => {
|
||||
copyToClipboard(`${url}/clips/${clip.value!.id}`);
|
||||
os.success();
|
||||
},
|
||||
}, {
|
||||
icon: 'ti ti-code',
|
||||
|
@@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -849,7 +849,6 @@ function exportLog() {
|
||||
l: DropAndFusionGame.serializeLogs(logs),
|
||||
});
|
||||
copyToClipboard(data);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function updateSettings<
|
||||
|
@@ -25,7 +25,7 @@ import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialo
|
||||
import { $i } from '@/i.js';
|
||||
|
||||
const props = defineProps<{
|
||||
emoji: Misskey.entities.EmojiSimple;
|
||||
emoji: Misskey.entities.EmojiSimple;
|
||||
}>();
|
||||
|
||||
function menu(ev) {
|
||||
@@ -38,7 +38,6 @@ function menu(ev) {
|
||||
icon: 'ti ti-copy',
|
||||
action: () => {
|
||||
copyToClipboard(`:${props.emoji.name}:`);
|
||||
os.success();
|
||||
},
|
||||
}, {
|
||||
text: i18n.ts.info,
|
||||
|
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noNotes }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -129,7 +129,6 @@ function copyLink() {
|
||||
if (!flash.value) return;
|
||||
|
||||
copyToClipboard(`${url}/play/${flash.value.id}`);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function shareWithNavigator() {
|
||||
|
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination ref="paginationComponent" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noFollowRequests }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -111,7 +111,6 @@ function fetchPost() {
|
||||
|
||||
function copyLink() {
|
||||
copyToClipboard(`${url}/gallery/${post.value.id}`);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function share() {
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header><MkPageHeader/></template>
|
||||
<MkSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200">
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
|
||||
<div :class="$style.text">
|
||||
<i class="ti ti-alert-triangle"></i>
|
||||
{{ i18n.ts.nothing }}
|
||||
@@ -36,12 +36,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, shallowRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import MkInviteCode from '@/components/MkInviteCode.vue';
|
||||
import { definePage } from '@/page.js';
|
||||
import { serverErrorImageUrl, instance } from '@/instance.js';
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer v-if="error != null" :contentMax="1200">
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
|
||||
<p :class="$style.text">
|
||||
<i class="ti ti-alert-triangle"></i>
|
||||
{{ i18n.ts.nothing }}
|
||||
|
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div>
|
||||
<div v-if="antennas.length === 0" class="empty">
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div class="_gaps">
|
||||
<div v-if="items.length === 0" class="empty">
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<div>
|
||||
<div class="_fullinfo">
|
||||
<img :src="notFoundImageUrl" class="_ghost"/>
|
||||
<img :src="notFoundImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.notFoundDescription }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -190,7 +190,6 @@ function copyLink() {
|
||||
if (!page.value) return;
|
||||
|
||||
copyToClipboard(`${url}/@${page.value.user.username}/pages/${page.value.name}`);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function shareWithNote() {
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
|
||||
<MkSpacer v-if="error != null" :contentMax="1200">
|
||||
<div :class="$style.root">
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||
<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
|
||||
<p :class="$style.text">
|
||||
<i class="ti ti-alert-triangle"></i>
|
||||
{{ error }}
|
||||
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div v-if="role">{{ role.description }}</div>
|
||||
<MkUserList v-if="visible" :pagination="users" :extractor="(item) => item.user"/>
|
||||
<div v-else-if="!visible" class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkSpacer v-else-if="tab === 'timeline'" :contentMax="700">
|
||||
<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
|
||||
<div v-else-if="!visible" class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
@@ -38,12 +38,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { instanceName } from '@@/js/config.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import MkUserList from '@/components/MkUserList.vue';
|
||||
import { definePage } from '@/page.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import { instanceName } from '@@/js/config.js';
|
||||
import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
|
@@ -58,6 +58,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['text', 'selectable']">
|
||||
<MkPreferenceContainer k="makeEveryTextElementsSelectable">
|
||||
<MkSwitch v-model="makeEveryTextElementsSelectable">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.makeEveryTextElementsSelectable }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts._settings.makeEveryTextElementsSelectable_description }}</template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']">
|
||||
@@ -122,6 +131,7 @@ const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
|
||||
const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
|
||||
const contextMenu = prefer.model('contextMenu');
|
||||
const menuStyle = prefer.model('menuStyle');
|
||||
const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable');
|
||||
|
||||
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||
@@ -147,6 +157,7 @@ watch([
|
||||
contextMenu,
|
||||
fontSize,
|
||||
useSystemFont,
|
||||
makeEveryTextElementsSelectable,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<FormPagination ref="list" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</FormLink>
|
||||
|
||||
<MkFolder :defaultOpen="true">
|
||||
<template #label><SearchLabel>{{ i18n.ts.manage }}</SearchLabel></template>
|
||||
<template #label>{{ i18n.ts.manage }}</template>
|
||||
|
||||
<MkPagination :pagination="pagination">
|
||||
<template #default="{items}">
|
||||
|
@@ -27,7 +27,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
<MkFooterSpacer/>
|
||||
</mkstickycontainer>
|
||||
</template>
|
||||
|
||||
|
@@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination :pagination="renoteMutingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination :pagination="mutingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -146,7 +146,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPagination :pagination="blockingPagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.noUsers }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -89,7 +89,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #caption>
|
||||
<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
|
||||
<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div>
|
||||
<div v-if="instance.federation !== 'none'"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
|
||||
</template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
@@ -33,12 +33,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import JSON5 from 'json5';
|
||||
import type { Theme } from '@/theme.js';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { getBuiltinThemesRef } from '@/theme.js';
|
||||
import type { Theme } from '@/theme.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import * as os from '@/os.js';
|
||||
import { getThemes, removeTheme } from '@/theme-store.js';
|
||||
@@ -63,7 +63,6 @@ const selectedThemeCode = computed(() => {
|
||||
|
||||
function copyThemeCode() {
|
||||
copyToClipboard(selectedThemeCode.value);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function uninstall() {
|
||||
|
@@ -46,7 +46,16 @@ export const PREF_DEF = {
|
||||
},
|
||||
widgets: {
|
||||
accountDependent: true,
|
||||
default: [] as {
|
||||
default: [{
|
||||
name: 'calendar',
|
||||
id: 'a', place: 'right', data: {},
|
||||
}, {
|
||||
name: 'notifications',
|
||||
id: 'b', place: 'right', data: {},
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: 'c', place: 'right', data: {},
|
||||
}] as {
|
||||
name: string;
|
||||
id: string;
|
||||
place: string | null;
|
||||
@@ -313,6 +322,9 @@ export const PREF_DEF = {
|
||||
defaultFollowWithReplies: {
|
||||
default: false,
|
||||
},
|
||||
makeEveryTextElementsSelectable: {
|
||||
default: DEFAULT_DEVICE_KIND === 'desktop',
|
||||
},
|
||||
plugins: {
|
||||
default: [] as Plugin[],
|
||||
},
|
||||
|
@@ -5,7 +5,6 @@
|
||||
|
||||
import { inject } from 'vue';
|
||||
import type { IRouter } from '@/nirax.js';
|
||||
import { Router } from '@/nirax.js';
|
||||
import { mainRouter } from '@/router/main.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
@@ -14,7 +13,7 @@ import { DI } from '@/di.js';
|
||||
* あらかじめ{@link setupRouter}を実行しておく必要がある({@link provide}により{@link IRouter}のインスタンスを注入可能であるならばこの限りではない)
|
||||
*/
|
||||
export function useRouter(): IRouter {
|
||||
return inject<Router | null>(DI.router, null) ?? mainRouter;
|
||||
return inject(DI.router, null) ?? mainRouter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -81,6 +81,11 @@ html {
|
||||
&.useSystemFont {
|
||||
font-family: system-ui;
|
||||
}
|
||||
|
||||
&:not(.forceSelectableAll) {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
html._themeChanging_ {
|
||||
@@ -120,6 +125,8 @@ a {
|
||||
textarea, input {
|
||||
tap-highlight-color: transparent;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
optgroup, option {
|
||||
@@ -184,6 +191,16 @@ rt {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
._selectable {
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
._selectableAtomic {
|
||||
user-select: all;
|
||||
-webkit-user-select: all;
|
||||
}
|
||||
|
||||
._noSelect {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
@@ -197,11 +214,6 @@ rt {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
._ghost {
|
||||
@extend ._noSelect;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
._modalBg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -424,6 +436,10 @@ rt {
|
||||
color: var(--MI_THEME-link);
|
||||
}
|
||||
|
||||
._love {
|
||||
color: var(--MI_THEME-love);
|
||||
}
|
||||
|
||||
._caption {
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
|
@@ -4,7 +4,10 @@
|
||||
*/
|
||||
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { ComputedRef, Ref } from 'vue';
|
||||
import type { Component, ComputedRef, Ref } from 'vue';
|
||||
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
||||
|
||||
type ComponentProps<T extends Component> = { [K in keyof CP<T>]: CP<T>[K] | Ref<CP<T>[K]> };
|
||||
|
||||
type MenuRadioOptionsDef = Record<string, any>;
|
||||
|
||||
@@ -20,11 +23,12 @@ export type MenuSwitch = { type: 'switch', ref: Ref<boolean>, text: string, icon
|
||||
export type MenuButton = { type?: 'button', text: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean | ComputedRef<boolean>, avatar?: Misskey.entities.User; action: MenuAction };
|
||||
export type MenuRadio = { type: 'radio', text: string, icon?: string, ref: Ref<MenuRadioOptionsDef[keyof MenuRadioOptionsDef]>, options: MenuRadioOptionsDef, disabled?: boolean | Ref<boolean> };
|
||||
export type MenuRadioOption = { type: 'radioOption', text: string, action: MenuAction; active?: boolean | ComputedRef<boolean> };
|
||||
export type MenuComponent<T extends Component = any> = { type: 'component', component: T, props?: ComponentProps<T> };
|
||||
export type MenuParent = { type: 'parent', text: string, icon?: string, children: MenuItem[] | (() => Promise<MenuItem[]> | MenuItem[]) };
|
||||
|
||||
export type MenuPending = { type: 'pending' };
|
||||
|
||||
type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuParent;
|
||||
type OuterPromiseMenuItem = Promise<MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuParent>;
|
||||
type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuComponent | MenuParent;
|
||||
type OuterPromiseMenuItem = Promise<MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuComponent | MenuParent>;
|
||||
export type MenuItem = OuterMenuItem | OuterPromiseMenuItem;
|
||||
export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuParent;
|
||||
export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuComponent | MenuParent;
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div class="body">
|
||||
<div class="left">
|
||||
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
|
||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/>
|
||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/>
|
||||
</button>
|
||||
<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact>
|
||||
<i class="ti ti-home ti-fw"></i>
|
||||
|
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div class="divider"></div>
|
||||
<div class="about">
|
||||
<button v-click-anime class="item _button" @click="openInstanceMenu">
|
||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/>
|
||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/>
|
||||
</button>
|
||||
</div>
|
||||
<!--<MisskeyLogo class="misskey"/>-->
|
||||
|
@@ -144,19 +144,6 @@ if (window.innerWidth < 1024) {
|
||||
|
||||
document.documentElement.style.overflowY = 'scroll';
|
||||
|
||||
if (prefer.s.widgets.length === 0) {
|
||||
prefer.commit('widgets', [{
|
||||
name: 'calendar',
|
||||
id: 'a', place: null, data: {},
|
||||
}, {
|
||||
name: 'notifications',
|
||||
id: 'b', place: null, data: {},
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: 'c', place: null, data: {},
|
||||
}]);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
isDesktop.value = (window.innerWidth >= DESKTOP_THRESHOLD);
|
||||
|
@@ -178,19 +178,6 @@ if (window.innerWidth > 1024) {
|
||||
}
|
||||
}
|
||||
|
||||
if (prefer.s.widgets.length === 0) {
|
||||
prefer.commit('widgets', [{
|
||||
name: 'calendar',
|
||||
id: 'a', place: 'right', data: {},
|
||||
}, {
|
||||
name: 'notifications',
|
||||
id: 'b', place: 'right', data: {},
|
||||
}, {
|
||||
name: 'trends',
|
||||
id: 'c', place: 'right', data: {},
|
||||
}]);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (!isDesktop.value) {
|
||||
window.addEventListener('resize', () => {
|
||||
|
@@ -248,12 +248,12 @@ export const searchIndexes: SearchIndexItem[] = [
|
||||
keywords: ['login', 'signin'],
|
||||
},
|
||||
{
|
||||
id: 'lUtOQbnwi',
|
||||
id: '5RbESWefG',
|
||||
label: i18n.ts._accountSettings.makeNotesFollowersOnlyBefore,
|
||||
keywords: ['follower', i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription],
|
||||
},
|
||||
{
|
||||
id: '83WWcjwS9',
|
||||
id: 'hdzwDs3qd',
|
||||
label: i18n.ts._accountSettings.makeNotesHiddenBefore,
|
||||
keywords: ['hidden', i18n.ts._accountSettings.makeNotesHiddenBeforeDescription],
|
||||
},
|
||||
@@ -791,7 +791,7 @@ export const searchIndexes: SearchIndexItem[] = [
|
||||
},
|
||||
{
|
||||
id: '5VSGOVYR0',
|
||||
label: i18n.ts.manage,
|
||||
label: i18n.ts._settings.webhook,
|
||||
keywords: ['webhook'],
|
||||
},
|
||||
],
|
||||
@@ -897,22 +897,27 @@ export const searchIndexes: SearchIndexItem[] = [
|
||||
keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
|
||||
},
|
||||
{
|
||||
id: '1fV9WINCQ',
|
||||
id: 'b1GYEEJeh',
|
||||
label: i18n.ts._settings.makeEveryTextElementsSelectable,
|
||||
keywords: ['text', 'selectable'],
|
||||
},
|
||||
{
|
||||
id: 'vVLxwINTJ',
|
||||
label: i18n.ts.menuStyle,
|
||||
keywords: ['menu', 'style', 'popup', 'drawer'],
|
||||
},
|
||||
{
|
||||
id: 'mLQzlKUNu',
|
||||
id: '14cMhMLHL',
|
||||
label: i18n.ts._contextMenu.title,
|
||||
keywords: ['contextmenu', 'system', 'native'],
|
||||
},
|
||||
{
|
||||
id: 'yP96aA3j9',
|
||||
id: 'oSo4LXMX9',
|
||||
label: i18n.ts.fontSize,
|
||||
keywords: ['font', 'size'],
|
||||
},
|
||||
{
|
||||
id: 'jQeiMopFE',
|
||||
id: '7LQSAThST',
|
||||
label: i18n.ts.useSystemFont,
|
||||
keywords: ['font', 'system', 'native'],
|
||||
},
|
||||
|
@@ -3,9 +3,15 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
/**
|
||||
* Clipboardに値をコピー(TODO: 文字列以外も対応)
|
||||
*/
|
||||
export function copyToClipboard(input: string | null) {
|
||||
if (input) navigator.clipboard.writeText(input);
|
||||
if (input) {
|
||||
navigator.clipboard.writeText(input);
|
||||
os.toast(i18n.ts.copiedToClipboard);
|
||||
}
|
||||
};
|
||||
|
@@ -65,7 +65,6 @@ function toggleSensitive(file: Misskey.entities.DriveFile) {
|
||||
|
||||
function copyUrl(file: Misskey.entities.DriveFile) {
|
||||
copyToClipboard(file.url);
|
||||
os.success();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -150,7 +149,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyFileId,
|
||||
action: () => {
|
||||
copyToClipboard(file.id);
|
||||
|
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
|
||||
import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
|
||||
import * as os from '@/os.js';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
|
||||
|
||||
const MOBILE_THRESHOLD = 500;
|
||||
|
||||
@@ -74,7 +74,6 @@ export function genEmbedCode(entity: EmbeddableEntity, id: string, params?: Embe
|
||||
// PCじゃない場合はコードカスタマイズ画面を出さずにそのままコピー
|
||||
if (window.innerWidth < MOBILE_THRESHOLD) {
|
||||
copyToClipboard(getEmbedCode(`/embed/${entity}/${id}`, _params));
|
||||
os.success();
|
||||
} else {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkEmbedCodeGenDialog.vue')), {
|
||||
entity,
|
||||
|
@@ -157,7 +157,6 @@ export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string):
|
||||
text,
|
||||
action: (): void => {
|
||||
copyToClipboard(`${url}/notes/${note.id}`);
|
||||
os.success();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -237,7 +236,6 @@ export function getNoteMenu(props: {
|
||||
|
||||
function copyContent(): void {
|
||||
copyToClipboard(appearNote.text);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function togglePin(pin: boolean): void {
|
||||
@@ -324,7 +322,6 @@ export function getNoteMenu(props: {
|
||||
text: i18n.ts.copyRemoteLink,
|
||||
action: () => {
|
||||
copyToClipboard(appearNote.url ?? appearNote.uri);
|
||||
os.success();
|
||||
},
|
||||
}, {
|
||||
icon: 'ti ti-external-link',
|
||||
@@ -483,7 +480,6 @@ export function getNoteMenu(props: {
|
||||
text: i18n.ts.copyRemoteLink,
|
||||
action: () => {
|
||||
copyToClipboard(appearNote.url ?? appearNote.uri);
|
||||
os.success();
|
||||
},
|
||||
}, {
|
||||
icon: 'ti ti-external-link',
|
||||
@@ -512,11 +508,10 @@ export function getNoteMenu(props: {
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyNoteId,
|
||||
action: () => {
|
||||
copyToClipboard(appearNote.id);
|
||||
os.success();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@@ -401,7 +401,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
||||
|
||||
if (prefer.s.devMode) {
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
icon: 'ti ti-id',
|
||||
icon: 'ti ti-hash',
|
||||
text: i18n.ts.copyUserId,
|
||||
action: () => {
|
||||
copyToClipboard(user.id);
|
||||
|
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkAvatar v-for="user in users" :key="user.id" :user="user.followee" link preview></MkAvatar>
|
||||
</div>
|
||||
<div v-else :class="$style.bdayFFallback">
|
||||
<img :src="infoImageUrl" class="_ghost" :class="$style.bdayFFallbackImage"/>
|
||||
<img :src="infoImageUrl" draggable="false" :class="$style.bdayFFallbackImage"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div class="ekmkgxbj">
|
||||
<MkLoading v-if="fetching"/>
|
||||
<div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
<div>{{ i18n.ts.nothing }}</div>
|
||||
</div>
|
||||
<div v-else :class="$style.feed">
|
||||
@@ -25,13 +25,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { url as base } from '@@/js/config.js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { useWidgetPropsManager } from './widget.js';
|
||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||
import type { GetFormResultType } from '@/utility/form.js';
|
||||
import MkContainer from '@/components/MkContainer.vue';
|
||||
import { url as base } from '@@/js/config.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
|
||||
const name = 'rss';
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2025.3.2-beta.1",
|
||||
"version": "2025.3.2-beta.3",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
|
72
pnpm-lock.yaml
generated
72
pnpm-lock.yaml
generated
@@ -98,15 +98,6 @@ importers:
|
||||
'@aws-sdk/lib-storage':
|
||||
specifier: 3.749.0
|
||||
version: 3.749.0(@aws-sdk/client-s3@3.749.0)
|
||||
'@bull-board/api':
|
||||
specifier: 6.7.7
|
||||
version: 6.7.7(@bull-board/ui@6.7.7)
|
||||
'@bull-board/fastify':
|
||||
specifier: 6.7.7
|
||||
version: 6.7.7
|
||||
'@bull-board/ui':
|
||||
specifier: 6.7.7
|
||||
version: 6.7.7
|
||||
'@discordapp/twemoji':
|
||||
specifier: 15.1.0
|
||||
version: 15.1.0
|
||||
@@ -1868,17 +1859,6 @@ packages:
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@bull-board/api@6.7.7':
|
||||
resolution: {integrity: sha512-jSBe+aeNs41T/BUJNutKSM17hJigDLoOaAzUZyFwT63/Yt00hiqQo90THXmDi3vGdXtTruGlkrC9OhVxBKo1eQ==}
|
||||
peerDependencies:
|
||||
'@bull-board/ui': 6.7.7
|
||||
|
||||
'@bull-board/fastify@6.7.7':
|
||||
resolution: {integrity: sha512-EVxpRW0ag/tVPqfHm6s/3P6X5DnzKTr0J5lI1EgOvpe+OXavaPWkU0iLPepyyC6ls+k0djdkd1ix1PP/caqufw==}
|
||||
|
||||
'@bull-board/ui@6.7.7':
|
||||
resolution: {integrity: sha512-QU3OkaJVIUt1SpRRV/XxPSTD9tmJcwBWi1oa4ND+qGWQigQ2H1PYfpQCNFOlyW8qCkBwkSDn8pLwlyGbppWqJg==}
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
|
||||
|
||||
@@ -5911,11 +5891,6 @@ packages:
|
||||
ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
ejs@3.1.10:
|
||||
resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
electron-to-chromium@1.5.83:
|
||||
resolution: {integrity: sha512-LcUDPqSt+V0QmI47XLzZrz5OqILSMGsPFkDYus22rIbgorSvBYEFqq854ltTmUdHkY92FSdAAvsh4jWEULMdfQ==}
|
||||
|
||||
@@ -6366,9 +6341,6 @@ packages:
|
||||
resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
filelist@1.0.4:
|
||||
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
|
||||
|
||||
filename-reserved-regex@3.0.0:
|
||||
resolution: {integrity: sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@@ -7238,11 +7210,6 @@ packages:
|
||||
resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
jake@10.8.5:
|
||||
resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
jest-changed-files@29.7.0:
|
||||
resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -9151,9 +9118,6 @@ packages:
|
||||
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
redis-info@3.1.0:
|
||||
resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==}
|
||||
|
||||
redis-lock@0.1.4:
|
||||
resolution: {integrity: sha512-7/+zu86XVQfJVx1nHTzux5reglDiyUCDwmW7TSlvVezfhH2YLc/Rc8NE0ejQG+8/0lwKzm29/u/4+ogKeLosiA==}
|
||||
engines: {node: '>=0.6'}
|
||||
@@ -11618,23 +11582,6 @@ snapshots:
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@bull-board/api@6.7.7(@bull-board/ui@6.7.7)':
|
||||
dependencies:
|
||||
'@bull-board/ui': 6.7.7
|
||||
redis-info: 3.1.0
|
||||
|
||||
'@bull-board/fastify@6.7.7':
|
||||
dependencies:
|
||||
'@bull-board/api': 6.7.7(@bull-board/ui@6.7.7)
|
||||
'@bull-board/ui': 6.7.7
|
||||
'@fastify/static': 8.1.0
|
||||
'@fastify/view': 10.0.2
|
||||
ejs: 3.1.10
|
||||
|
||||
'@bull-board/ui@6.7.7':
|
||||
dependencies:
|
||||
'@bull-board/api': 6.7.7(@bull-board/ui@6.7.7)
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
dependencies:
|
||||
cookie: 0.7.2
|
||||
@@ -16450,10 +16397,6 @@ snapshots:
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
ejs@3.1.10:
|
||||
dependencies:
|
||||
jake: 10.8.5
|
||||
|
||||
electron-to-chromium@1.5.83: {}
|
||||
|
||||
emittery@0.13.1: {}
|
||||
@@ -17214,10 +17157,6 @@ snapshots:
|
||||
token-types: 6.0.0
|
||||
uint8array-extras: 1.4.0
|
||||
|
||||
filelist@1.0.4:
|
||||
dependencies:
|
||||
minimatch: 5.1.2
|
||||
|
||||
filename-reserved-regex@3.0.0: {}
|
||||
|
||||
filenamify@6.0.0:
|
||||
@@ -18134,13 +18073,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
jake@10.8.5:
|
||||
dependencies:
|
||||
async: 3.2.4
|
||||
chalk: 4.1.2
|
||||
filelist: 1.0.4
|
||||
minimatch: 3.1.2
|
||||
|
||||
jest-changed-files@29.7.0:
|
||||
dependencies:
|
||||
execa: 5.1.1
|
||||
@@ -20546,10 +20478,6 @@ snapshots:
|
||||
|
||||
redis-errors@1.2.0: {}
|
||||
|
||||
redis-info@3.1.0:
|
||||
dependencies:
|
||||
lodash: 4.17.21
|
||||
|
||||
redis-lock@0.1.4: {}
|
||||
|
||||
redis-parser@3.0.0:
|
||||
|
Reference in New Issue
Block a user