Compare commits
12 Commits
2025.3.2-b
...
view-trans
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76dc7affe0 | ||
|
|
1c9d9923f4 | ||
|
|
c8db2043b5 | ||
|
|
f40c5f27dd | ||
|
|
386494dd6c | ||
|
|
f5c946b44d | ||
|
|
5fe23d3f69 | ||
|
|
7d86efd087 | ||
|
|
361f810da8 | ||
|
|
be16622de2 | ||
|
|
f930cd7842 | ||
|
|
f1014bc7f7 |
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
@@ -24,6 +24,9 @@ updates:
|
|||||||
aws-sdk:
|
aws-sdk:
|
||||||
patterns:
|
patterns:
|
||||||
- "@aws-sdk/*"
|
- "@aws-sdk/*"
|
||||||
|
bull-board:
|
||||||
|
patterns:
|
||||||
|
- "@bull-board/*"
|
||||||
nestjs:
|
nestjs:
|
||||||
patterns:
|
patterns:
|
||||||
- "@nestjs/*"
|
- "@nestjs/*"
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
## 2025.3.2
|
## 2025.3.2
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。
|
-
|
||||||
- Misskeyネイティブでダッシュボードを実装予定です
|
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: 設定の管理が強化されました
|
- Feat: 設定の管理が強化されました
|
||||||
@@ -14,9 +13,6 @@
|
|||||||
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
|
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
|
||||||
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
|
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
|
||||||
- Enhance: テーマ設定画面のデザインを改善
|
- Enhance: テーマ設定画面のデザインを改善
|
||||||
- Enhance: 投稿フォームの設定メニューを改良
|
|
||||||
- 投稿フォームをリセットできるように
|
|
||||||
- 文字数カウントを復活
|
|
||||||
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
|||||||
@@ -1190,7 +1190,7 @@ pastAnnouncements: "Informes passats"
|
|||||||
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
youHaveUnreadAnnouncements: "Tens informes per llegir."
|
||||||
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
|
||||||
replies: "Respostes"
|
replies: "Respostes"
|
||||||
renotes: "Impulsos"
|
renotes: "Impulsar"
|
||||||
loadReplies: "Mostrar les respostes"
|
loadReplies: "Mostrar les respostes"
|
||||||
loadConversation: "Mostrar la conversació "
|
loadConversation: "Mostrar la conversació "
|
||||||
pinnedList: "Llista fixada"
|
pinnedList: "Llista fixada"
|
||||||
@@ -1326,17 +1326,7 @@ restore: "Restaurar "
|
|||||||
syncBetweenDevices: "Sincronització entre dispositius"
|
syncBetweenDevices: "Sincronització entre dispositius"
|
||||||
preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu"
|
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?"
|
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ó"
|
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:
|
_settings:
|
||||||
driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius."
|
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."
|
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."
|
||||||
@@ -1354,7 +1344,6 @@ _settings:
|
|||||||
preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències."
|
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."
|
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."
|
soundsBanner: "Configuració dels sons que reproduirà el client."
|
||||||
timelineAndNote: "Línia de temps i nota"
|
|
||||||
_preferencesProfile:
|
_preferencesProfile:
|
||||||
profileName: "Nom del perfil"
|
profileName: "Nom del perfil"
|
||||||
profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
|
profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
|
||||||
@@ -2067,7 +2056,7 @@ _theme:
|
|||||||
hashtag: "Etiqueta"
|
hashtag: "Etiqueta"
|
||||||
mention: "Menció"
|
mention: "Menció"
|
||||||
mentionMe: "Mencions (jo)"
|
mentionMe: "Mencions (jo)"
|
||||||
renote: "Impulsar"
|
renote: "Renotar"
|
||||||
modalBg: "Fons del modal"
|
modalBg: "Fons del modal"
|
||||||
divider: "Divisor"
|
divider: "Divisor"
|
||||||
scrollbarHandle: "Maneta de la barra de desplaçament"
|
scrollbarHandle: "Maneta de la barra de desplaçament"
|
||||||
@@ -2509,7 +2498,7 @@ _notification:
|
|||||||
follow: "Segueix-me"
|
follow: "Segueix-me"
|
||||||
mention: "Menció"
|
mention: "Menció"
|
||||||
reply: "Respostes"
|
reply: "Respostes"
|
||||||
renote: "Impulsos"
|
renote: "Impulsar"
|
||||||
quote: "Citar"
|
quote: "Citar"
|
||||||
reaction: "Reaccions"
|
reaction: "Reaccions"
|
||||||
pollEnded: "Enquesta terminada"
|
pollEnded: "Enquesta terminada"
|
||||||
@@ -2524,7 +2513,7 @@ _notification:
|
|||||||
_actions:
|
_actions:
|
||||||
followBack: "També et segueix"
|
followBack: "També et segueix"
|
||||||
reply: "Respondre"
|
reply: "Respondre"
|
||||||
renote: "Impulsar"
|
renote: "Impulsos"
|
||||||
_deck:
|
_deck:
|
||||||
alwaysShowMainColumn: "Mostrar sempre la columna principal"
|
alwaysShowMainColumn: "Mostrar sempre la columna principal"
|
||||||
columnAlign: "Alinea les columnes"
|
columnAlign: "Alinea les columnes"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pin: "An dein Profil anheften"
|
|||||||
unpin: "Von deinem Profil lösen"
|
unpin: "Von deinem Profil lösen"
|
||||||
copyContent: "Inhalt kopieren"
|
copyContent: "Inhalt kopieren"
|
||||||
copyLink: "Link kopieren"
|
copyLink: "Link kopieren"
|
||||||
copyRemoteLink: "Remote-Link kopieren"
|
copyRemoteLink: "Renote-Link kopieren"
|
||||||
copyLinkRenote: "Renote-Link kopieren"
|
copyLinkRenote: "Renote-Link kopieren"
|
||||||
delete: "Löschen"
|
delete: "Löschen"
|
||||||
deleteAndEdit: "Löschen und Bearbeiten"
|
deleteAndEdit: "Löschen und Bearbeiten"
|
||||||
|
|||||||
20
locales/index.d.ts
vendored
20
locales/index.d.ts
vendored
@@ -2810,10 +2810,6 @@ export interface Locale extends ILocale {
|
|||||||
* コピー
|
* コピー
|
||||||
*/
|
*/
|
||||||
"copy": string;
|
"copy": string;
|
||||||
/**
|
|
||||||
* クリップボードにコピーされました
|
|
||||||
*/
|
|
||||||
"copiedToClipboard": string;
|
|
||||||
/**
|
/**
|
||||||
* メトリクス
|
* メトリクス
|
||||||
*/
|
*/
|
||||||
@@ -5350,10 +5346,6 @@ export interface Locale extends ILocale {
|
|||||||
* 投稿フォーム
|
* 投稿フォーム
|
||||||
*/
|
*/
|
||||||
"postForm": string;
|
"postForm": string;
|
||||||
/**
|
|
||||||
* 文字数
|
|
||||||
*/
|
|
||||||
"textCount": string;
|
|
||||||
"_emojiPalette": {
|
"_emojiPalette": {
|
||||||
/**
|
/**
|
||||||
* パレット
|
* パレット
|
||||||
@@ -5441,14 +5433,6 @@ export interface Locale extends ILocale {
|
|||||||
* タイムラインとノート
|
* タイムラインとノート
|
||||||
*/
|
*/
|
||||||
"timelineAndNote": string;
|
"timelineAndNote": string;
|
||||||
/**
|
|
||||||
* 全てのテキスト要素を選択可能にする
|
|
||||||
*/
|
|
||||||
"makeEveryTextElementsSelectable": string;
|
|
||||||
/**
|
|
||||||
* 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
|
|
||||||
*/
|
|
||||||
"makeEveryTextElementsSelectable_description": string;
|
|
||||||
};
|
};
|
||||||
"_preferencesProfile": {
|
"_preferencesProfile": {
|
||||||
/**
|
/**
|
||||||
@@ -9793,10 +9777,6 @@ export interface Locale extends ILocale {
|
|||||||
* ログイン
|
* ログイン
|
||||||
*/
|
*/
|
||||||
"login": string;
|
"login": string;
|
||||||
/**
|
|
||||||
* アクセストークンの作成
|
|
||||||
*/
|
|
||||||
"createToken": string;
|
|
||||||
/**
|
/**
|
||||||
* 通知のテスト
|
* 通知のテスト
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name}が「{word}」について何かを言いまし
|
|||||||
makeActive: "アクティブにする"
|
makeActive: "アクティブにする"
|
||||||
display: "表示"
|
display: "表示"
|
||||||
copy: "コピー"
|
copy: "コピー"
|
||||||
copiedToClipboard: "クリップボードにコピーされました"
|
|
||||||
metrics: "メトリクス"
|
metrics: "メトリクス"
|
||||||
overview: "概要"
|
overview: "概要"
|
||||||
logs: "ログ"
|
logs: "ログ"
|
||||||
@@ -1333,7 +1332,6 @@ preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
|
|||||||
paste: "ペースト"
|
paste: "ペースト"
|
||||||
emojiPalette: "絵文字パレット"
|
emojiPalette: "絵文字パレット"
|
||||||
postForm: "投稿フォーム"
|
postForm: "投稿フォーム"
|
||||||
textCount: "文字数"
|
|
||||||
|
|
||||||
_emojiPalette:
|
_emojiPalette:
|
||||||
palettes: "パレット"
|
palettes: "パレット"
|
||||||
@@ -1359,8 +1357,6 @@ _settings:
|
|||||||
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
|
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
|
||||||
soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
|
soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
|
||||||
timelineAndNote: "タイムラインとノート"
|
timelineAndNote: "タイムラインとノート"
|
||||||
makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
|
|
||||||
makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
|
|
||||||
|
|
||||||
_preferencesProfile:
|
_preferencesProfile:
|
||||||
profileName: "プロファイル名"
|
profileName: "プロファイル名"
|
||||||
@@ -2588,7 +2584,6 @@ _notification:
|
|||||||
achievementEarned: "実績の獲得"
|
achievementEarned: "実績の獲得"
|
||||||
exportCompleted: "エクスポートが完了した"
|
exportCompleted: "エクスポートが完了した"
|
||||||
login: "ログイン"
|
login: "ログイン"
|
||||||
createToken: "アクセストークンの作成"
|
|
||||||
test: "通知のテスト"
|
test: "通知のテスト"
|
||||||
app: "連携アプリからの通知"
|
app: "連携アプリからの通知"
|
||||||
|
|
||||||
|
|||||||
@@ -746,7 +746,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。
|
|||||||
public: "公开"
|
public: "公开"
|
||||||
private: "私密"
|
private: "私密"
|
||||||
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
|
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
|
||||||
manageAccessTokens: "管理访问令牌"
|
manageAccessTokens: "管理 Access Tokens"
|
||||||
accountInfo: "账户信息"
|
accountInfo: "账户信息"
|
||||||
notesCount: "帖子数量"
|
notesCount: "帖子数量"
|
||||||
repliesCount: "回复数量"
|
repliesCount: "回复数量"
|
||||||
@@ -1339,24 +1339,13 @@ _emojiPalette:
|
|||||||
paletteForReaction: "回应用调色板"
|
paletteForReaction: "回应用调色板"
|
||||||
_settings:
|
_settings:
|
||||||
driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。"
|
driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。"
|
||||||
pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。"
|
|
||||||
notificationsBanner: "可在此设置从服务器接收的通知的种类和范围,以及推送通知的设置。"
|
|
||||||
api: "API"
|
api: "API"
|
||||||
webhook: "Webhook"
|
webhook: "Webhook"
|
||||||
serviceConnection: "连接服务"
|
|
||||||
serviceConnectionBanner: "可在此管理用于连接外部应用或服务的访问令牌及 Webhook。"
|
|
||||||
accountData: "账户数据"
|
|
||||||
accountDataBanner: "可在此导入或导出帐户数据的存档。"
|
|
||||||
muteAndBlockBanner: "可在此设置隐藏内容,或限制指定用户能进行的操作。"
|
|
||||||
accessibilityBanner: "可在此设置客户端的显示及动态效果等辅助设置。"
|
|
||||||
privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。"
|
privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。"
|
||||||
securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。"
|
securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。"
|
||||||
preferencesBanner: "可在此设置客户端的整体运作行为。"
|
preferencesBanner: "可在此设置客户端的整体运作行为。"
|
||||||
appearanceBanner: "可在此设置客户端的外观及显示方式。"
|
appearanceBanner: "可在此设置客户端的外观及显示方式。"
|
||||||
soundsBanner: "可在此设置客户端播放的声音。"
|
soundsBanner: "可在此设置客户端播放的声音。"
|
||||||
timelineAndNote: "时间线和帖子"
|
|
||||||
makeEveryTextElementsSelectable: "使所有的文字均可选择"
|
|
||||||
makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。"
|
|
||||||
_preferencesProfile:
|
_preferencesProfile:
|
||||||
profileName: "配置名"
|
profileName: "配置名"
|
||||||
profileNameDescription: "请指定用于识别此设备的名称"
|
profileNameDescription: "请指定用于识别此设备的名称"
|
||||||
@@ -2521,7 +2510,6 @@ _notification:
|
|||||||
achievementEarned: "取得的成就"
|
achievementEarned: "取得的成就"
|
||||||
exportCompleted: "已完成导出"
|
exportCompleted: "已完成导出"
|
||||||
login: "登录"
|
login: "登录"
|
||||||
createToken: "创建访问令牌"
|
|
||||||
test: "测试通知"
|
test: "测试通知"
|
||||||
app: "关联应用的通知"
|
app: "关联应用的通知"
|
||||||
_actions:
|
_actions:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.3.2-beta.3",
|
"version": "2025.3.2-beta.1",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -69,6 +69,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "3.749.0",
|
"@aws-sdk/client-s3": "3.749.0",
|
||||||
"@aws-sdk/lib-storage": "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",
|
"@discordapp/twemoji": "15.1.0",
|
||||||
"@fastify/accepts": "5.0.2",
|
"@fastify/accepts": "5.0.2",
|
||||||
"@fastify/cookie": "11.0.2",
|
"@fastify/cookie": "11.0.2",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import cors from '@fastify/cors';
|
import cors from '@fastify/cors';
|
||||||
import multipart from '@fastify/multipart';
|
import multipart from '@fastify/multipart';
|
||||||
|
import fastifyCookie from '@fastify/cookie';
|
||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
@@ -56,6 +57,8 @@ export class ApiServerService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.register(fastifyCookie, {});
|
||||||
|
|
||||||
// Prevent cache
|
// Prevent cache
|
||||||
fastify.addHook('onRequest', (request, reply, done) => {
|
fastify.addHook('onRequest', (request, reply, done) => {
|
||||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||||
|
|||||||
@@ -7,12 +7,16 @@ import { randomUUID } from 'node:crypto';
|
|||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
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 ms from 'ms';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import pug from 'pug';
|
import pug from 'pug';
|
||||||
import { In, IsNull } from 'typeorm';
|
import { In, IsNull } from 'typeorm';
|
||||||
import fastifyStatic from '@fastify/static';
|
import fastifyStatic from '@fastify/static';
|
||||||
import fastifyView from '@fastify/view';
|
import fastifyView from '@fastify/view';
|
||||||
|
import fastifyCookie from '@fastify/cookie';
|
||||||
import fastifyProxy from '@fastify/http-proxy';
|
import fastifyProxy from '@fastify/http-proxy';
|
||||||
import vary from 'vary';
|
import vary from 'vary';
|
||||||
import htmlSafeJsonStringify from 'htmlescape';
|
import htmlSafeJsonStringify from 'htmlescape';
|
||||||
@@ -217,6 +221,64 @@ export class ClientServerService {
|
|||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
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, {
|
fastify.register(fastifyView, {
|
||||||
root: _dirname + '/views',
|
root: _dirname + '/views',
|
||||||
engine: {
|
engine: {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
process.env.NODE_ENV = 'test';
|
process.env.NODE_ENV = 'test';
|
||||||
|
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
|
import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
|
||||||
import type { SimpleGetResponse } from '../utils.js';
|
import type { SimpleGetResponse } from '../utils.js';
|
||||||
import type * as misskey from 'misskey-js';
|
import type * as misskey from 'misskey-js';
|
||||||
|
|
||||||
@@ -156,20 +156,20 @@ describe('Webリソース', () => {
|
|||||||
|
|
||||||
describe(' has entry such ', () => {
|
describe(' has entry such ', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
post(alice, { text: '**a**' });
|
post(alice, { text: "**a**" })
|
||||||
});
|
});
|
||||||
|
|
||||||
test('MFMを含まない。', async () => {
|
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;
|
const _body: unknown = content.body;
|
||||||
// JSONフィードのときは改めて文字列化する
|
// 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**')) {
|
if (body.includes("**a**")) {
|
||||||
throw new Error('MFM shouldn\'t be included');
|
throw new Error("MFM shouldn't be included");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
|
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
|
||||||
@@ -180,6 +180,24 @@ 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 }) => {
|
describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
|
||||||
test('はGETできない。', async () => await notOk({
|
test('はGETできない。', async () => await notOk({
|
||||||
path,
|
path,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export type SystemWebhookPayload = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
type: string;
|
type: string;
|
||||||
body: any;
|
body: any;
|
||||||
};
|
}
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
export const port = config.port;
|
export const port = config.port;
|
||||||
@@ -45,6 +45,10 @@ export const host = new URL(config.url).host;
|
|||||||
export const WEBHOOK_HOST = 'http://localhost:15080';
|
export const WEBHOOK_HOST = 'http://localhost:15080';
|
||||||
export const WEBHOOK_PORT = 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']> = {
|
export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = {
|
||||||
endpoint: E,
|
endpoint: E,
|
||||||
parameters: P,
|
parameters: P,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="notFoundImageUrl" draggable="false"/>
|
<img :src="notFoundImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.notFoundDescription }}</div>
|
<div>{{ i18n.ts.notFoundDescription }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,5 +20,5 @@ import { i18n } from '@/i18n.js';
|
|||||||
|
|
||||||
const serverMetadata = inject(DI.serverMetadata)!;
|
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>
|
</script>
|
||||||
|
|||||||
@@ -234,10 +234,6 @@ export async function common(createVue: () => App<Element>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefer.s.makeEveryTextElementsSelectable) {
|
|
||||||
document.documentElement.classList.add('forceSelectableAll');
|
|
||||||
}
|
|
||||||
|
|
||||||
//#region Fetch user
|
//#region Fetch user
|
||||||
if ($i && $i.token) {
|
if ($i && $i.token) {
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ function forward() {
|
|||||||
|
|
||||||
function showMenu(ev: MouseEvent) {
|
function showMenu(ev: MouseEvent) {
|
||||||
os.popupMenu([{
|
os.popupMenu([{
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: 'Copy ID',
|
text: 'Copy ID',
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(props.report.id);
|
copyToClipboard(props.report.id);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination :pagination="pagination">
|
<MkPagination :pagination="pagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.notFound }}</div>
|
<div>{{ i18n.ts.notFound }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
|
||||||
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
import MkChannelPreview from '@/components/MkChannelPreview.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
|
|||||||
|
|
||||||
function copy() {
|
function copy() {
|
||||||
copyToClipboard(props.code);
|
copyToClipboard(props.code);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
<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"/>
|
<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
|
||||||
</div>
|
</div>
|
||||||
<header v-if="title" :class="$style.title" class="_selectable"><Mfm :text="title"/></header>
|
<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
|
||||||
<div v-if="text" :class="$style.text" class="_selectable"><Mfm :text="text"/></div>
|
<div v-if="text" :class="$style.text"><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">
|
<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 v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
|
||||||
<template #caption>
|
<template #caption>
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ function onContextmenu(ev: MouseEvent) {
|
|||||||
}];
|
}];
|
||||||
if (prefer.s.devMode) {
|
if (prefer.s.devMode) {
|
||||||
menu = menu.concat([{ type: 'divider' }, {
|
menu = menu.concat([{ type: 'divider' }, {
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyFolderId,
|
text: i18n.ts.copyFolderId,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(props.folder.id);
|
copyToClipboard(props.folder.id);
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ function generate() {
|
|||||||
|
|
||||||
function doCopy() {
|
function doCopy() {
|
||||||
copyToClipboard(result.value);
|
copyToClipboard(result.value);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="_fullinfo">
|
<div v-else class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
|||||||
@@ -11,18 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
@touchmove.passive="touchMove"
|
@touchmove.passive="touchMove"
|
||||||
@touchend.passive="touchEnd"
|
@touchend.passive="touchEnd"
|
||||||
>
|
>
|
||||||
<Transition
|
<!-- 【注意】slot内の最上位要素に動的にkeyを設定すること -->
|
||||||
:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
|
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
|
||||||
:enterActiveClass="$style.swipeAnimation_enterActive"
|
<slot></slot>
|
||||||
:leaveActiveClass="$style.swipeAnimation_leaveActive"
|
|
||||||
:enterFromClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_enterFrom : $style.swipeAnimationRight_enterFrom"
|
|
||||||
:leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo"
|
|
||||||
:style="`--swipe: ${pullDistance}px;`"
|
|
||||||
>
|
|
||||||
<!-- 【注意】slot内の最上位要素に動的にkeyを設定すること -->
|
|
||||||
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
|
|
||||||
<slot></slot>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -14,34 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined"
|
:enterToClass="prefer.s.animation && props.transition?.enterToClass || undefined"
|
||||||
:leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined"
|
:leaveFromClass="prefer.s.animation && props.transition?.leaveFromClass || undefined"
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/>
|
||||||
v-show="hide"
|
<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"/>
|
||||||
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>
|
</TransitionGroup>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="[$style.root, { [$style.warn]: warn }]" class="_selectable">
|
<div :class="[$style.root, { [$style.warn]: warn }]">
|
||||||
<i v-if="warn" class="ti ti-alert-triangle" :class="$style.i"></i>
|
<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>
|
<i v-else class="ti ti-info-circle" :class="$style.i"></i>
|
||||||
<div><slot></slot></div>
|
<div><slot></slot></div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_selectable">
|
<div>
|
||||||
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
|
<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 :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]">
|
||||||
<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
|
<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>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
|
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
|
||||||
import { debounce } from 'throttle-debounce';
|
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
|
||||||
import type { InputHTMLAttributes } from 'vue';
|
import type { InputHTMLAttributes } from 'vue';
|
||||||
import type { SuggestionType } from '@/utility/autocomplete.js';
|
import { debounce } from 'throttle-debounce';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { Autocomplete } from '@/utility/autocomplete.js';
|
import { Autocomplete } from '@/utility/autocomplete.js';
|
||||||
|
import type { SuggestionType } from '@/utility/autocomplete.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string | number | null;
|
modelValue: string | number | null;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div :class="$style.items">
|
<div :class="$style.items">
|
||||||
<div>
|
<div>
|
||||||
<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
|
<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
|
||||||
<div class="_selectableAtomic">{{ invite.code }}</div>
|
<div>{{ invite.code }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="moderator">
|
<div v-if="moderator">
|
||||||
<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
|
<div :class="$style.label">{{ i18n.ts.inviteCodeCreator }}</div>
|
||||||
@@ -90,6 +90,7 @@ function deleteCode() {
|
|||||||
|
|
||||||
function copyInviteCode() {
|
function copyInviteCode() {
|
||||||
copyToClipboard(props.invite.code);
|
copyToClipboard(props.invite.code);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div :class="$style.key">
|
<div :class="$style.key">
|
||||||
<slot name="key"></slot>
|
<slot name="key"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.value" class="_selectable">
|
<div :class="$style.value">
|
||||||
<slot name="value"></slot>
|
<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>
|
<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>
|
</div>
|
||||||
@@ -31,6 +31,7 @@ const props = withDefaults(defineProps<{
|
|||||||
|
|
||||||
const copy_ = () => {
|
const copy_ = () => {
|
||||||
copyToClipboard(props.copy);
|
copyToClipboard(props.copy);
|
||||||
|
os.success();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ function showMenu(ev: MouseEvent) {
|
|||||||
|
|
||||||
if (prefer.s.devMode) {
|
if (prefer.s.devMode) {
|
||||||
menu.push({ type: 'divider' }, {
|
menu.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyFileId,
|
text: i18n.ts.copyFileId,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(props.audio.id);
|
copyToClipboard(props.audio.id);
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ function showMenu(ev: MouseEvent) {
|
|||||||
|
|
||||||
if (prefer.s.devMode) {
|
if (prefer.s.devMode) {
|
||||||
menuItems.push({ type: 'divider' }, {
|
menuItems.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyFileId,
|
text: i18n.ts.copyFileId,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(props.image.id);
|
copyToClipboard(props.image.id);
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ function showMenu(ev: MouseEvent) {
|
|||||||
|
|
||||||
if (prefer.s.devMode) {
|
if (prefer.s.devMode) {
|
||||||
menu.push({ type: 'divider' }, {
|
menu.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyFileId,
|
text: i18n.ts.copyFileId,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(props.video.id);
|
copyToClipboard(props.video.id);
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
|
<span v-else-if="item.type === 'pending'" role="menuitem" tabindex="0" :class="[$style.pending, $style.item]">
|
||||||
<span><MkEllipsis/></span>
|
<span><MkEllipsis/></span>
|
||||||
</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
|
<MkA
|
||||||
v-else-if="item.type === 'link'"
|
v-else-if="item.type === 'link'"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
||||||
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
|
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
|
||||||
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
|
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock" :style="{ viewTransitionName: transitionName }"/>
|
||||||
<div :class="$style.main">
|
<div :class="$style.main">
|
||||||
<MkNoteHeader :note="appearNote" :mini="true"/>
|
<MkNoteHeader :note="appearNote" :mini="true"/>
|
||||||
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
|
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
|
||||||
@@ -76,13 +76,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:emojiUrls="appearNote.emojis"
|
:emojiUrls="appearNote.emojis"
|
||||||
:enableEmojiMenu="true"
|
:enableEmojiMenu="true"
|
||||||
:enableEmojiMenuReaction="true"
|
:enableEmojiMenuReaction="true"
|
||||||
class="_selectable"
|
|
||||||
/>
|
/>
|
||||||
<div v-if="translating || translation" :class="$style.translation">
|
<div v-if="translating || translation" :class="$style.translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
<div v-else-if="translation">
|
<div v-else-if="translation">
|
||||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
|
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -178,7 +177,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, inject, onMounted, ref, shallowRef, watch, provide } from 'vue';
|
import { computed, inject, onMounted, ref, shallowRef, watch, provide, reactive, nextTick } from 'vue';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
@@ -224,6 +223,7 @@ import { focusPrev, focusNext } from '@/utility/focus.js';
|
|||||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.js';
|
import { getPluginHandlers } from '@/plugin.js';
|
||||||
|
import { prepareViewTransition } from '@/page.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
@@ -235,7 +235,18 @@ const props = withDefaults(defineProps<{
|
|||||||
mock: false,
|
mock: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const transitionNames = reactive({
|
||||||
|
avatar: '',
|
||||||
|
});
|
||||||
|
|
||||||
provide(DI.mock, props.mock);
|
provide(DI.mock, props.mock);
|
||||||
|
provide(DI.navHook, (path, flag) => {
|
||||||
|
const names = prepareViewTransition(path);
|
||||||
|
transitionNames.avatar = names.avatar;
|
||||||
|
nextTick(() => {
|
||||||
|
router.push(path, flag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'reaction', emoji: string): void;
|
(ev: 'reaction', emoji: string): void;
|
||||||
@@ -854,6 +865,8 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||||||
position: sticky !important;
|
position: sticky !important;
|
||||||
top: calc(22px + var(--MI-stickyTop, 0px));
|
top: calc(22px + var(--MI-stickyTop, 0px));
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
|
contain: paint;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
<article :class="$style.note" @contextmenu.stop="onContextmenu">
|
<article :class="$style.note" @contextmenu.stop="onContextmenu">
|
||||||
<header :class="$style.noteHeader">
|
<header :class="$style.noteHeader">
|
||||||
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
|
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview :style="{ viewTransitionName: transitionName }"/>
|
||||||
<div :class="$style.noteHeaderBody">
|
<div :class="$style.noteHeaderBody">
|
||||||
<div>
|
<div>
|
||||||
<MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
|
<MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
|
||||||
@@ -97,14 +97,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:emojiUrls="appearNote.emojis"
|
:emojiUrls="appearNote.emojis"
|
||||||
:enableEmojiMenu="true"
|
:enableEmojiMenu="true"
|
||||||
:enableEmojiMenuReaction="true"
|
:enableEmojiMenuReaction="true"
|
||||||
class="_selectable"
|
|
||||||
/>
|
/>
|
||||||
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
|
||||||
<div v-if="translating || translation" :class="$style.translation">
|
<div v-if="translating || translation" :class="$style.translation">
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkLoading v-if="translating" mini/>
|
||||||
<div v-else-if="translation">
|
<div v-else-if="translation">
|
||||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" class="_selectable"/>
|
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="appearNote.files && appearNote.files.length > 0">
|
<div v-if="appearNote.files && appearNote.files.length > 0">
|
||||||
@@ -256,6 +255,7 @@ import { isEnabledUrlPreview } from '@/instance.js';
|
|||||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { getPluginHandlers } from '@/plugin.js';
|
import { getPluginHandlers } from '@/plugin.js';
|
||||||
|
import { prepareViewTransition } from '@/page.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
@@ -264,6 +264,8 @@ const props = withDefaults(defineProps<{
|
|||||||
initialTab: 'replies',
|
initialTab: 'replies',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const transitionName = prepareViewTransition('note-noteDetailed', props.note.id).avatar;
|
||||||
|
|
||||||
const inChannel = inject('inChannel', null);
|
const inChannel = inject('inChannel', null);
|
||||||
|
|
||||||
const note = ref(deepClone(props.note));
|
const note = ref(deepClone(props.note));
|
||||||
@@ -670,6 +672,8 @@ function loadConversation() {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 58px;
|
width: 58px;
|
||||||
height: 58px;
|
height: 58px;
|
||||||
|
|
||||||
|
contain: paint;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noteHeaderBody {
|
.noteHeaderBody {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad">
|
<MkPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noNotes }}</div>
|
<div>{{ i18n.ts.noNotes }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -33,10 +33,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { shallowRef } from 'vue';
|
import { shallowRef } from 'vue';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
|
||||||
import MkNote from '@/components/MkNote.vue';
|
import MkNote from '@/components/MkNote.vue';
|
||||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
<MkPagination ref="pagingComponent" :pagination="pagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noNotifications }}</div>
|
<div>{{ i18n.ts.noNotifications }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div v-else-if="empty" key="_empty_" class="empty">
|
<div v-else-if="empty" key="_empty_" class="empty">
|
||||||
<slot name="empty">
|
<slot name="empty">
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
<!--
|
|
||||||
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>
|
||||||
<div :class="$style.headerRight">
|
<div :class="$style.headerRight">
|
||||||
<template v-if="!(channel != null && fixed)">
|
<template v-if="!(channel != null && fixed)">
|
||||||
<button v-if="channel == null" ref="visibilityButton" v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility">
|
<button v-if="channel == null" ref="visibilityButton" v-click-anime 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 === 'public'"><i class="ti ti-world"></i></span>
|
||||||
<span v-if="visibility === 'home'"><i class="ti ti-home"></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>
|
<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span>
|
||||||
@@ -32,11 +32,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
|
<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<button v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
|
<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">
|
||||||
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
|
<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
|
||||||
<span v-else><i class="ti ti-rocket-off"></i></span>
|
<span v-else><i class="ti ti-rocket-off"></i></span>
|
||||||
</button>
|
</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 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 v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
|
<button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
|
||||||
<div :class="$style.submitInner">
|
<div :class="$style.submitInner">
|
||||||
<template v-if="posted"></template>
|
<template v-if="posted"></template>
|
||||||
@@ -107,11 +111,9 @@ import { toASCII } from 'punycode.js';
|
|||||||
import { host, url } from '@@/js/config.js';
|
import { host, url } from '@@/js/config.js';
|
||||||
import type { ShallowRef } from 'vue';
|
import type { ShallowRef } from 'vue';
|
||||||
import type { PostFormProps } from '@/types/post-form.js';
|
import type { PostFormProps } from '@/types/post-form.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
|
||||||
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
|
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
|
||||||
import MkNotePreview from '@/components/MkNotePreview.vue';
|
import MkNotePreview from '@/components/MkNotePreview.vue';
|
||||||
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
||||||
import XTextCounter from '@/components/MkPostForm.TextCounter.vue';
|
|
||||||
import MkPollEditor from '@/components/MkPollEditor.vue';
|
import MkPollEditor from '@/components/MkPollEditor.vue';
|
||||||
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
||||||
import { erase, unique } from '@/utility/array.js';
|
import { erase, unique } from '@/utility/array.js';
|
||||||
@@ -169,7 +171,6 @@ const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
|
|||||||
const cwInputEl = shallowRef<HTMLInputElement | null>(null);
|
const cwInputEl = shallowRef<HTMLInputElement | null>(null);
|
||||||
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
|
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
|
||||||
const visibilityButton = shallowRef<HTMLElement>();
|
const visibilityButton = shallowRef<HTMLElement>();
|
||||||
const otherSettingsButton = shallowRef<HTMLElement>();
|
|
||||||
|
|
||||||
const posting = ref(false);
|
const posting = ref(false);
|
||||||
const posted = ref(false);
|
const posted = ref(false);
|
||||||
@@ -555,47 +556,6 @@ async function toggleReactionAcceptance() {
|
|||||||
reactionAcceptance.value = select.result;
|
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) {
|
function pushVisibleUser(user: Misskey.entities.UserDetailed) {
|
||||||
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
|
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
|
||||||
visibleUsers.value.push(user);
|
visibleUsers.value.push(user);
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
|
|||||||
|
|
||||||
if (prefer.s.devMode) {
|
if (prefer.s.devMode) {
|
||||||
menuItems.push({ type: 'divider' }, {
|
menuItems.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyFileId,
|
text: i18n.ts.copyFileId,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(file.id);
|
copyToClipboard(file.id);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
@click="toggleReaction()"
|
@click="toggleReaction()"
|
||||||
@contextmenu.prevent.stop="menu"
|
@contextmenu.prevent.stop="menu"
|
||||||
>
|
>
|
||||||
<MkReactionIcon style="pointer-events: none;" :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
<MkReactionIcon :class="prefer.s.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
||||||
<span :class="$style.count">{{ count }}</span>
|
<span :class="$style.count">{{ count }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="_selectable">
|
<div>
|
||||||
<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
|
<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;">
|
<div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;">
|
||||||
<textarea
|
<textarea
|
||||||
@@ -38,10 +38,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
|
import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
|
||||||
import { debounce } from 'throttle-debounce';
|
import { debounce } from 'throttle-debounce';
|
||||||
import type { SuggestionType } from '@/utility/autocomplete.js';
|
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { Autocomplete } from '@/utility/autocomplete.js';
|
import { Autocomplete } from '@/utility/autocomplete.js';
|
||||||
|
import type { SuggestionType } from '@/utility/autocomplete.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: string | null;
|
modelValue: string | null;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination :pagination="pagination">
|
<MkPagination :pagination="pagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noUsers }}</div>
|
<div>{{ i18n.ts.noUsers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -21,9 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
|
||||||
import MkUserInfo from '@/components/MkUserInfo.vue';
|
import MkUserInfo from '@/components/MkUserInfo.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import * as os from '@/os.js';
|
|||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
|
import { DI } from '@/di.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
to: string;
|
to: string;
|
||||||
@@ -37,6 +38,7 @@ const el = shallowRef<HTMLElement>();
|
|||||||
defineExpose({ $el: el });
|
defineExpose({ $el: el });
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const navHook = inject(DI.navHook, null);
|
||||||
|
|
||||||
const active = computed(() => {
|
const active = computed(() => {
|
||||||
if (props.activeClass == null) return false;
|
if (props.activeClass == null) return false;
|
||||||
@@ -99,6 +101,10 @@ function nav(ev: MouseEvent) {
|
|||||||
return openWindow();
|
return openWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push(props.to, ev.ctrlKey ? 'forcePage' : null);
|
if (navHook != null) {
|
||||||
|
navHook(props.to, ev.ctrlKey ? 'forcePage' : null);
|
||||||
|
} else {
|
||||||
|
router.push(props.to, ev.ctrlKey ? 'forcePage' : null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
translate: getDecorationOffset(decoration),
|
translate: getDecorationOffset(decoration),
|
||||||
}"
|
}"
|
||||||
alt=""
|
alt=""
|
||||||
draggable="false"
|
|
||||||
style="-webkit-user-drag: none;"
|
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
|
||||||
src="/client-assets/dummy.png"
|
src="/client-assets/dummy.png"
|
||||||
:title="alt"
|
:title="alt"
|
||||||
draggable="false"
|
|
||||||
style="-webkit-user-drag: none;"
|
|
||||||
/>
|
/>
|
||||||
<span v-else-if="errored">:{{ customEmojiName }}:</span>
|
<span v-else-if="errored">:{{ customEmojiName }}:</span>
|
||||||
<img
|
<img
|
||||||
@@ -20,7 +18,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:alt="alt"
|
:alt="alt"
|
||||||
:title="alt"
|
:title="alt"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
draggable="false"
|
|
||||||
@error="errored = true"
|
@error="errored = true"
|
||||||
@load="errored = false"
|
@load="errored = false"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
@@ -100,6 +97,7 @@ function onClick(ev: MouseEvent) {
|
|||||||
icon: 'ti ti-copy',
|
icon: 'ti ti-copy',
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(`:${props.name}:`);
|
copyToClipboard(`:${props.name}:`);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -159,7 +157,6 @@ async function edit(name: string) {
|
|||||||
.root {
|
.root {
|
||||||
height: 2em;
|
height: 2em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
-webkit-user-drag: none;
|
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ function onClick(ev: MouseEvent) {
|
|||||||
icon: 'ti ti-copy',
|
icon: 'ti ti-copy',
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(props.emoji);
|
copyToClipboard(props.emoji);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
|
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
|
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||||
<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p>
|
<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>
|
<MkButton :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
32
packages/frontend/src/components/global/MkFooterSpacer.vue
Normal file
32
packages/frontend/src/components/global/MkFooterSpacer.vue
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<!--
|
||||||
|
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>
|
||||||
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
:exclude="pageCacheController"
|
:exclude="pageCacheController"
|
||||||
>
|
>
|
||||||
<Suspense :timeout="0">
|
<Suspense :timeout="0">
|
||||||
<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
|
<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)" :style="{ viewTransitionName: viewId }"/>
|
||||||
|
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
@@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
|
import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
import type { IRouter, Resolved, RouteDef } from '@/nirax.js';
|
import type { IRouter, Resolved, RouteDef } from '@/nirax.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { globalEvents } from '@/events.js';
|
import { globalEvents } from '@/events.js';
|
||||||
@@ -40,6 +41,12 @@ if (router == null) {
|
|||||||
const currentDepth = inject(DI.routerCurrentDepth, 0);
|
const currentDepth = inject(DI.routerCurrentDepth, 0);
|
||||||
provide(DI.routerCurrentDepth, currentDepth + 1);
|
provide(DI.routerCurrentDepth, currentDepth + 1);
|
||||||
|
|
||||||
|
const viewId = uuid();
|
||||||
|
provide(DI.viewId, viewId);
|
||||||
|
|
||||||
|
const viewTransitionId = ref(uuid());
|
||||||
|
provide(DI.viewTransitionId, viewTransitionId);
|
||||||
|
|
||||||
function resolveNested(current: Resolved, d = 0): Resolved | null {
|
function resolveNested(current: Resolved, d = 0): Resolved | null {
|
||||||
if (!props.nested) return current;
|
if (!props.nested) return current;
|
||||||
|
|
||||||
@@ -59,18 +66,30 @@ const currentPageComponent = shallowRef('component' in current.route ? current.r
|
|||||||
const currentPageProps = ref(current.props);
|
const currentPageProps = ref(current.props);
|
||||||
const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props)));
|
const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props)));
|
||||||
|
|
||||||
function onChange({ resolved, key: newKey }) {
|
async function onChange({ resolved, key: newKey }) {
|
||||||
const current = resolveNested(resolved);
|
const current = resolveNested(resolved);
|
||||||
if (current == null || 'redirect' in current.route) return;
|
if (current == null || 'redirect' in current.route) return;
|
||||||
currentPageComponent.value = current.route.component;
|
|
||||||
currentPageProps.value = current.props;
|
|
||||||
key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
|
|
||||||
|
|
||||||
|
viewTransitionId.value = uuid();
|
||||||
|
await nextTick();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// ページ遷移完了後に再びキャッシュを有効化
|
console.log('onChange', viewTransitionId.value);
|
||||||
if (clearCacheRequested.value) {
|
document.startViewTransition(() => new Promise((res) => {
|
||||||
clearCacheRequested.value = false;
|
console.log('startViewTransition', viewTransitionId.value);
|
||||||
}
|
currentPageComponent.value = current.route.component;
|
||||||
|
currentPageProps.value = current.props;
|
||||||
|
key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
|
||||||
|
|
||||||
|
nextTick(async () => {
|
||||||
|
//res();
|
||||||
|
setTimeout(res, 100);
|
||||||
|
|
||||||
|
// ページ遷移完了後に再びキャッシュを有効化
|
||||||
|
if (clearCacheRequested.value) {
|
||||||
|
clearCacheRequested.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,3 +119,31 @@ onBeforeUnmount(() => {
|
|||||||
router.removeListener('change', onChange);
|
router.removeListener('change', onChange);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@keyframes fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-out {
|
||||||
|
to { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-from-right {
|
||||||
|
from { transform: translateX(300px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-to-left {
|
||||||
|
to { transform: translateX(-300px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-old(v-bind(viewId)) {
|
||||||
|
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
|
||||||
|
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(v-bind(viewId)) {
|
||||||
|
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
|
||||||
|
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import MkError from './global/MkError.vue';
|
|||||||
import MkAd from './global/MkAd.vue';
|
import MkAd from './global/MkAd.vue';
|
||||||
import MkPageHeader from './global/MkPageHeader.vue';
|
import MkPageHeader from './global/MkPageHeader.vue';
|
||||||
import MkSpacer from './global/MkSpacer.vue';
|
import MkSpacer from './global/MkSpacer.vue';
|
||||||
|
import MkFooterSpacer from './global/MkFooterSpacer.vue';
|
||||||
import MkStickyContainer from './global/MkStickyContainer.vue';
|
import MkStickyContainer from './global/MkStickyContainer.vue';
|
||||||
import MkLazy from './global/MkLazy.vue';
|
import MkLazy from './global/MkLazy.vue';
|
||||||
import SearchMarker from './global/SearchMarker.vue';
|
import SearchMarker from './global/SearchMarker.vue';
|
||||||
@@ -54,6 +55,7 @@ export const components = {
|
|||||||
MkAd: MkAd,
|
MkAd: MkAd,
|
||||||
MkPageHeader: MkPageHeader,
|
MkPageHeader: MkPageHeader,
|
||||||
MkSpacer: MkSpacer,
|
MkSpacer: MkSpacer,
|
||||||
|
MkFooterSpacer: MkFooterSpacer,
|
||||||
MkStickyContainer: MkStickyContainer,
|
MkStickyContainer: MkStickyContainer,
|
||||||
MkLazy: MkLazy,
|
MkLazy: MkLazy,
|
||||||
SearchMarker: SearchMarker,
|
SearchMarker: SearchMarker,
|
||||||
@@ -81,6 +83,7 @@ declare module '@vue/runtime-core' {
|
|||||||
MkAd: typeof MkAd;
|
MkAd: typeof MkAd;
|
||||||
MkPageHeader: typeof MkPageHeader;
|
MkPageHeader: typeof MkPageHeader;
|
||||||
MkSpacer: typeof MkSpacer;
|
MkSpacer: typeof MkSpacer;
|
||||||
|
MkFooterSpacer: typeof MkFooterSpacer;
|
||||||
MkStickyContainer: typeof MkStickyContainer;
|
MkStickyContainer: typeof MkStickyContainer;
|
||||||
MkLazy: typeof MkLazy;
|
MkLazy: typeof MkLazy;
|
||||||
SearchMarker: typeof SearchMarker;
|
SearchMarker: typeof SearchMarker;
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { InjectionKey, Ref } from 'vue';
|
import type { InjectionKey, Ref } from 'vue';
|
||||||
import type { IRouter } from '@/nirax.js';
|
import type { IRouter, RouterFlag } from '@/nirax.js';
|
||||||
|
|
||||||
export const DI = {
|
export const DI = {
|
||||||
routerCurrentDepth: Symbol() as InjectionKey<number>,
|
routerCurrentDepth: Symbol() as InjectionKey<number>,
|
||||||
router: Symbol() as InjectionKey<IRouter>,
|
router: Symbol() as InjectionKey<IRouter>,
|
||||||
|
viewId: Symbol() as InjectionKey<string>,
|
||||||
|
viewTransitionId: Symbol() as InjectionKey<Ref<string>>,
|
||||||
mock: Symbol() as InjectionKey<boolean>,
|
mock: Symbol() as InjectionKey<boolean>,
|
||||||
|
navHook: Symbol() as InjectionKey<(path: string, flag?: RouterFlag) => void>,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,4 +5,4 @@
|
|||||||
|
|
||||||
import { numberFormat } from '@@/js/intl-const.js';
|
import { numberFormat } from '@@/js/intl-const.js';
|
||||||
|
|
||||||
export default (n?: number) => n == null ? 'N/A' : numberFormat.format(n);
|
export default n => n == null ? 'N/A' : numberFormat.format(n);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ interface RouteDefWithRedirect extends RouteDefBase {
|
|||||||
|
|
||||||
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
|
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
|
||||||
|
|
||||||
export type RouterFlag = 'forcePage';
|
export type RouterFlag = 'forcePage' | null;
|
||||||
|
|
||||||
type ParsedPath = (string | {
|
type ParsedPath = (string | {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Miss
|
|||||||
});
|
});
|
||||||
if (result === 'copy') {
|
if (result === 'copy') {
|
||||||
copyToClipboard(`Endpoint: ${endpoint}\nInfo: ${JSON.stringify(err.info)}\nDate: ${date}`);
|
copyToClipboard(`Endpoint: ${endpoint}\nInfo: ${JSON.stringify(err.info)}\nDate: ${date}`);
|
||||||
|
success();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
|
} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
|
import { computed, inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
|
||||||
import type { MaybeRefOrGetter, Ref } from 'vue';
|
import type { MaybeRefOrGetter, Ref } from 'vue';
|
||||||
|
import { DI } from '@/di.js';
|
||||||
|
|
||||||
export type PageMetadata = {
|
export type PageMetadata = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -69,3 +70,12 @@ export const injectReactiveMetadata = (): Ref<PageMetadata | null> => {
|
|||||||
const metadataRef = getMetadata();
|
const metadataRef = getMetadata();
|
||||||
return isRef(metadataRef) ? metadataRef : ref(null);
|
return isRef(metadataRef) ? metadataRef : ref(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function prepareViewTransition(type: string, id: string) {
|
||||||
|
const viewId = inject(DI.viewId);
|
||||||
|
const viewTransitionId = inject(DI.viewTransitionId);
|
||||||
|
return {
|
||||||
|
avatar: computed(() => 'adsfsdfsfg' + viewId + viewTransitionId.value + id),
|
||||||
|
//avatar: computed(() => 'adsfsdfsfg' + id),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkLoading v-if="!loaded"/>
|
<MkLoading v-if="!loaded"/>
|
||||||
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
|
<Transition :name="prefer.s.animation ? '_transition_zoom' : ''" appear>
|
||||||
<div v-show="loaded" :class="$style.root">
|
<div v-show="loaded" :class="$style.root">
|
||||||
<img :src="serverErrorImageUrl" draggable="false" :class="$style.img"/>
|
<img :src="serverErrorImageUrl" class="_ghost" :class="$style.img"/>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></div>
|
<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>
|
<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>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import * as config from '@@/js/config.js';
|
import type { Ref } from 'vue';
|
||||||
import XQueue from './queue.chart.vue';
|
import XQueue from './queue.chart.vue';
|
||||||
import XHeader from './_header_.vue';
|
import XHeader from './_header_.vue';
|
||||||
import type { Ref } from 'vue';
|
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
import * as config from '@@/js/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
@@ -54,7 +54,14 @@ function promoteAllQueues() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => [{
|
||||||
|
asFullButton: true,
|
||||||
|
icon: 'ti ti-external-link',
|
||||||
|
text: i18n.ts.dashboard,
|
||||||
|
handler: () => {
|
||||||
|
window.open(config.url + '/queue', '_blank', 'noopener');
|
||||||
|
},
|
||||||
|
}]);
|
||||||
|
|
||||||
const headerTabs = computed(() => [{
|
const headerTabs = computed(() => [{
|
||||||
key: 'deliver',
|
key: 'deliver',
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination :pagination="usersPagination">
|
<MkPagination :pagination="usersPagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noUsers }}</div>
|
<div>{{ i18n.ts.noUsers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ const headerActions = computed(() => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
copyToClipboard(`${url}/channels/${channel.value.id}`);
|
copyToClipboard(`${url}/channels/${channel.value.id}`);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
|
|||||||
text: i18n.ts.copyUrl,
|
text: i18n.ts.copyUrl,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(`${url}/clips/${clip.value!.id}`);
|
copyToClipboard(`${url}/clips/${clip.value!.id}`);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-code',
|
icon: 'ti ti-code',
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="_fullinfo">
|
<div v-else class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -849,6 +849,7 @@ function exportLog() {
|
|||||||
l: DropAndFusionGame.serializeLogs(logs),
|
l: DropAndFusionGame.serializeLogs(logs),
|
||||||
});
|
});
|
||||||
copyToClipboard(data);
|
copyToClipboard(data);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSettings<
|
function updateSettings<
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialo
|
|||||||
import { $i } from '@/i.js';
|
import { $i } from '@/i.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
emoji: Misskey.entities.EmojiSimple;
|
emoji: Misskey.entities.EmojiSimple;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function menu(ev) {
|
function menu(ev) {
|
||||||
@@ -38,6 +38,7 @@ function menu(ev) {
|
|||||||
icon: 'ti ti-copy',
|
icon: 'ti ti-copy',
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(`:${props.emoji.name}:`);
|
copyToClipboard(`:${props.emoji.name}:`);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.info,
|
text: i18n.ts.info,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination :pagination="pagination">
|
<MkPagination :pagination="pagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noNotes }}</div>
|
<div>{{ i18n.ts.noNotes }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ function copyLink() {
|
|||||||
if (!flash.value) return;
|
if (!flash.value) return;
|
||||||
|
|
||||||
copyToClipboard(`${url}/play/${flash.value.id}`);
|
copyToClipboard(`${url}/play/${flash.value.id}`);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
function shareWithNavigator() {
|
function shareWithNavigator() {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination ref="paginationComponent" :pagination="pagination">
|
<MkPagination ref="paginationComponent" :pagination="pagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noFollowRequests }}</div>
|
<div>{{ i18n.ts.noFollowRequests }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ function fetchPost() {
|
|||||||
|
|
||||||
function copyLink() {
|
function copyLink() {
|
||||||
copyToClipboard(`${url}/gallery/${post.value.id}`);
|
copyToClipboard(`${url}/gallery/${post.value.id}`);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
function share() {
|
function share() {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #header><MkPageHeader/></template>
|
<template #header><MkPageHeader/></template>
|
||||||
<MkSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200">
|
<MkSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
|
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||||
<div :class="$style.text">
|
<div :class="$style.text">
|
||||||
<i class="ti ti-alert-triangle"></i>
|
<i class="ti ti-alert-triangle"></i>
|
||||||
{{ i18n.ts.nothing }}
|
{{ i18n.ts.nothing }}
|
||||||
@@ -36,12 +36,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, shallowRef } from 'vue';
|
import { computed, ref, shallowRef } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { Paging } from '@/components/MkPagination.vue';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkPagination from '@/components/MkPagination.vue';
|
import MkPagination from '@/components/MkPagination.vue';
|
||||||
|
import type { Paging } from '@/components/MkPagination.vue';
|
||||||
import MkInviteCode from '@/components/MkInviteCode.vue';
|
import MkInviteCode from '@/components/MkInviteCode.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { serverErrorImageUrl, instance } from '@/instance.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>
|
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer v-if="error != null" :contentMax="1200">
|
<MkSpacer v-if="error != null" :contentMax="1200">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
|
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||||
<p :class="$style.text">
|
<p :class="$style.text">
|
||||||
<i class="ti ti-alert-triangle"></i>
|
<i class="ti ti-alert-triangle"></i>
|
||||||
{{ i18n.ts.nothing }}
|
{{ i18n.ts.nothing }}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div>
|
<div>
|
||||||
<div v-if="antennas.length === 0" class="empty">
|
<div v-if="antennas.length === 0" class="empty">
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div v-if="items.length === 0" class="empty">
|
<div v-if="items.length === 0" class="empty">
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="notFoundImageUrl" draggable="false"/>
|
<img :src="notFoundImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.notFoundDescription }}</div>
|
<div>{{ i18n.ts.notFoundDescription }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,40 +8,38 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<div>
|
<div>
|
||||||
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
<div v-if="note">
|
||||||
<div v-if="note">
|
<div v-if="showNext" class="_margin">
|
||||||
<div v-if="showNext" class="_margin">
|
<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
|
||||||
<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="_margin">
|
<div class="_margin">
|
||||||
<div v-if="!showNext" class="_buttons" :class="$style.loadNext">
|
<div v-if="!showNext" class="_buttons" :class="$style.loadNext">
|
||||||
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showNext = 'channel'"><i class="ti ti-chevron-up"></i> <i class="ti ti-device-tv"></i></MkButton>
|
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showNext = 'channel'"><i class="ti ti-chevron-up"></i> <i class="ti ti-device-tv"></i></MkButton>
|
||||||
<MkButton rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ti ti-chevron-up"></i> <i class="ti ti-user"></i></MkButton>
|
<MkButton rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ti ti-chevron-up"></i> <i class="ti ti-user"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="_margin _gaps_s">
|
<div class="_margin _gaps_s">
|
||||||
<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
|
<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
|
||||||
<MkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note"/>
|
<MkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="clips && clips.length > 0" class="_margin">
|
<div v-if="clips && clips.length > 0" class="_margin">
|
||||||
<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
|
<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkClipPreview v-for="item in clips" :key="item.id" :clip="item"/>
|
<MkClipPreview v-for="item in clips" :key="item.id" :clip="item"/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!showPrev" class="_buttons" :class="$style.loadPrev">
|
|
||||||
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showPrev = 'channel'"><i class="ti ti-chevron-down"></i> <i class="ti ti-device-tv"></i></MkButton>
|
|
||||||
<MkButton rounded :class="$style.loadButton" @click="showPrev = 'user'"><i class="ti ti-chevron-down"></i> <i class="ti ti-user"></i></MkButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!showPrev" class="_buttons" :class="$style.loadPrev">
|
||||||
<div v-if="showPrev" class="_margin">
|
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showPrev = 'channel'"><i class="ti ti-chevron-down"></i> <i class="ti ti-device-tv"></i></MkButton>
|
||||||
<MkNotes class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
<MkButton rounded :class="$style.loadButton" @click="showPrev = 'user'"><i class="ti ti-chevron-down"></i> <i class="ti ti-user"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkError v-else-if="error" @retry="fetchNote()"/>
|
|
||||||
<MkLoading v-else/>
|
<div v-if="showPrev" class="_margin">
|
||||||
</Transition>
|
<MkNotes class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MkError v-else-if="error" @retry="fetchNote()"/>
|
||||||
|
<MkLoading v-else/>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<div v-if="tab === 'all'" key="all">
|
<div v-if="tab === 'all'" key="all">
|
||||||
|
<div style="view-transition-name: a; contain: paint; margin: 64px;">BBBBBBBBB</div>
|
||||||
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab === 'mentions'" key="mention">
|
<div v-else-if="tab === 'mentions'" key="mention">
|
||||||
@@ -24,13 +25,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import { notificationTypes } from '@@/js/const.js';
|
||||||
import XNotifications from '@/components/MkNotifications.vue';
|
import XNotifications from '@/components/MkNotifications.vue';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { notificationTypes } from '@@/js/const.js';
|
|
||||||
|
|
||||||
const tab = ref('all');
|
const tab = ref('all');
|
||||||
const includeTypes = ref<string[] | null>(null);
|
const includeTypes = ref<string[] | null>(null);
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ function copyLink() {
|
|||||||
if (!page.value) return;
|
if (!page.value) return;
|
||||||
|
|
||||||
copyToClipboard(`${url}/@${page.value.user.username}/pages/${page.value.name}`);
|
copyToClipboard(`${url}/@${page.value.user.username}/pages/${page.value.name}`);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
function shareWithNote() {
|
function shareWithNote() {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer v-if="error != null" :contentMax="1200">
|
<MkSpacer v-if="error != null" :contentMax="1200">
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<img :class="$style.img" :src="serverErrorImageUrl" draggable="false"/>
|
<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
|
||||||
<p :class="$style.text">
|
<p :class="$style.text">
|
||||||
<i class="ti ti-alert-triangle"></i>
|
<i class="ti ti-alert-triangle"></i>
|
||||||
{{ error }}
|
{{ error }}
|
||||||
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div v-if="role">{{ role.description }}</div>
|
<div v-if="role">{{ role.description }}</div>
|
||||||
<MkUserList v-if="visible" :pagination="users" :extractor="(item) => item.user"/>
|
<MkUserList v-if="visible" :pagination="users" :extractor="(item) => item.user"/>
|
||||||
<div v-else-if="!visible" class="_fullinfo">
|
<div v-else-if="!visible" class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSpacer v-else-if="tab === 'timeline'" :contentMax="700">
|
<MkSpacer v-else-if="tab === 'timeline'" :contentMax="700">
|
||||||
<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
|
<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
|
||||||
<div v-else-if="!visible" class="_fullinfo">
|
<div v-else-if="!visible" class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
@@ -38,12 +38,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, ref } from 'vue';
|
import { computed, watch, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { instanceName } from '@@/js/config.js';
|
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import MkUserList from '@/components/MkUserList.vue';
|
import MkUserList from '@/components/MkUserList.vue';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkTimeline from '@/components/MkTimeline.vue';
|
||||||
|
import { instanceName } from '@@/js/config.js';
|
||||||
import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
|
import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
|||||||
@@ -58,15 +58,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</MkPreferenceContainer>
|
</MkPreferenceContainer>
|
||||||
</SearchMarker>
|
</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>
|
</div>
|
||||||
|
|
||||||
<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']">
|
<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']">
|
||||||
@@ -131,7 +122,6 @@ const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
|
|||||||
const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
|
const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
|
||||||
const contextMenu = prefer.model('contextMenu');
|
const contextMenu = prefer.model('contextMenu');
|
||||||
const menuStyle = prefer.model('menuStyle');
|
const menuStyle = prefer.model('menuStyle');
|
||||||
const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable');
|
|
||||||
|
|
||||||
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||||
@@ -157,7 +147,6 @@ watch([
|
|||||||
contextMenu,
|
contextMenu,
|
||||||
fontSize,
|
fontSize,
|
||||||
useSystemFont,
|
useSystemFont,
|
||||||
makeEveryTextElementsSelectable,
|
|
||||||
], async () => {
|
], async () => {
|
||||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<FormPagination ref="list" :pagination="pagination">
|
<FormPagination ref="list" :pagination="pagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</FormLink>
|
</FormLink>
|
||||||
|
|
||||||
<MkFolder :defaultOpen="true">
|
<MkFolder :defaultOpen="true">
|
||||||
<template #label>{{ i18n.ts.manage }}</template>
|
<template #label><SearchLabel>{{ i18n.ts.manage }}</SearchLabel></template>
|
||||||
|
|
||||||
<MkPagination :pagination="pagination">
|
<MkPagination :pagination="pagination">
|
||||||
<template #default="{items}">
|
<template #default="{items}">
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
|
<MkFooterSpacer/>
|
||||||
</mkstickycontainer>
|
</mkstickycontainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination :pagination="renoteMutingPagination">
|
<MkPagination :pagination="renoteMutingPagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noUsers }}</div>
|
<div>{{ i18n.ts.noUsers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination :pagination="mutingPagination">
|
<MkPagination :pagination="mutingPagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noUsers }}</div>
|
<div>{{ i18n.ts.noUsers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -146,7 +146,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkPagination :pagination="blockingPagination">
|
<MkPagination :pagination="blockingPagination">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<div class="_fullinfo">
|
<div class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.noUsers }}</div>
|
<div>{{ i18n.ts.noUsers }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template #caption>
|
<template #caption>
|
||||||
<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
|
<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><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>
|
</template>
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</SearchMarker>
|
</SearchMarker>
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
import type { Theme } from '@/theme.js';
|
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { getBuiltinThemesRef } from '@/theme.js';
|
import { getBuiltinThemesRef } from '@/theme.js';
|
||||||
|
import type { Theme } from '@/theme.js';
|
||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { getThemes, removeTheme } from '@/theme-store.js';
|
import { getThemes, removeTheme } from '@/theme-store.js';
|
||||||
@@ -63,6 +63,7 @@ const selectedThemeCode = computed(() => {
|
|||||||
|
|
||||||
function copyThemeCode() {
|
function copyThemeCode() {
|
||||||
copyToClipboard(selectedThemeCode.value);
|
copyToClipboard(selectedThemeCode.value);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
function uninstall() {
|
function uninstall() {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<MkSpacer :contentMax="800">
|
<MkSpacer :contentMax="800">
|
||||||
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
|
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
|
||||||
<div :key="src" ref="rootEl">
|
<div :key="src" ref="rootEl">
|
||||||
|
<div style="view-transition-name: a; contain: paint;">AAAAAAAAAA</div>
|
||||||
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
||||||
{{ i18n.ts._timelineDescription[src] }}
|
{{ i18n.ts._timelineDescription[src] }}
|
||||||
</MkInfo>
|
</MkInfo>
|
||||||
|
|||||||
@@ -46,16 +46,7 @@ export const PREF_DEF = {
|
|||||||
},
|
},
|
||||||
widgets: {
|
widgets: {
|
||||||
accountDependent: true,
|
accountDependent: true,
|
||||||
default: [{
|
default: [] as {
|
||||||
name: 'calendar',
|
|
||||||
id: 'a', place: 'right', data: {},
|
|
||||||
}, {
|
|
||||||
name: 'notifications',
|
|
||||||
id: 'b', place: 'right', data: {},
|
|
||||||
}, {
|
|
||||||
name: 'trends',
|
|
||||||
id: 'c', place: 'right', data: {},
|
|
||||||
}] as {
|
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
place: string | null;
|
place: string | null;
|
||||||
@@ -322,9 +313,6 @@ export const PREF_DEF = {
|
|||||||
defaultFollowWithReplies: {
|
defaultFollowWithReplies: {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
makeEveryTextElementsSelectable: {
|
|
||||||
default: DEFAULT_DEVICE_KIND === 'desktop',
|
|
||||||
},
|
|
||||||
plugins: {
|
plugins: {
|
||||||
default: [] as Plugin[],
|
default: [] as Plugin[],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -81,11 +81,6 @@ html {
|
|||||||
&.useSystemFont {
|
&.useSystemFont {
|
||||||
font-family: system-ui;
|
font-family: system-ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.forceSelectableAll) {
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html._themeChanging_ {
|
html._themeChanging_ {
|
||||||
@@ -125,8 +120,6 @@ a {
|
|||||||
textarea, input {
|
textarea, input {
|
||||||
tap-highlight-color: transparent;
|
tap-highlight-color: transparent;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
user-select: text;
|
|
||||||
-webkit-user-select: text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
optgroup, option {
|
optgroup, option {
|
||||||
@@ -191,16 +184,6 @@ rt {
|
|||||||
padding: 0.3em 0.5em;
|
padding: 0.3em 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
._selectable {
|
|
||||||
user-select: text;
|
|
||||||
-webkit-user-select: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
._selectableAtomic {
|
|
||||||
user-select: all;
|
|
||||||
-webkit-user-select: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
._noSelect {
|
._noSelect {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
@@ -214,6 +197,11 @@ rt {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
._ghost {
|
||||||
|
@extend ._noSelect;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
._modalBg {
|
._modalBg {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -436,10 +424,6 @@ rt {
|
|||||||
color: var(--MI_THEME-link);
|
color: var(--MI_THEME-link);
|
||||||
}
|
}
|
||||||
|
|
||||||
._love {
|
|
||||||
color: var(--MI_THEME-love);
|
|
||||||
}
|
|
||||||
|
|
||||||
._caption {
|
._caption {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import type { Component, ComputedRef, Ref } from 'vue';
|
import type { 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>;
|
type MenuRadioOptionsDef = Record<string, any>;
|
||||||
|
|
||||||
@@ -23,12 +20,11 @@ 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 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 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 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 MenuParent = { type: 'parent', text: string, icon?: string, children: MenuItem[] | (() => Promise<MenuItem[]> | MenuItem[]) };
|
||||||
|
|
||||||
export type MenuPending = { type: 'pending' };
|
export type MenuPending = { type: 'pending' };
|
||||||
|
|
||||||
type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuComponent | MenuParent;
|
type OuterMenuItem = MenuDivider | MenuNull | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuParent;
|
||||||
type OuterPromiseMenuItem = Promise<MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuComponent | MenuParent>;
|
type OuterPromiseMenuItem = Promise<MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuParent>;
|
||||||
export type MenuItem = OuterMenuItem | OuterPromiseMenuItem;
|
export type MenuItem = OuterMenuItem | OuterPromiseMenuItem;
|
||||||
export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuComponent | MenuParent;
|
export type InnerMenuItem = MenuDivider | MenuPending | MenuLabel | MenuLink | MenuA | MenuUser | MenuSwitch | MenuButton | MenuRadio | MenuRadioOption | MenuParent;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
|
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
|
||||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/>
|
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/>
|
||||||
</button>
|
</button>
|
||||||
<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact>
|
<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact>
|
||||||
<i class="ti ti-home ti-fw"></i>
|
<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="divider"></div>
|
||||||
<div class="about">
|
<div class="about">
|
||||||
<button v-click-anime class="item _button" @click="openInstanceMenu">
|
<button v-click-anime class="item _button" @click="openInstanceMenu">
|
||||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/>
|
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!--<MisskeyLogo class="misskey"/>-->
|
<!--<MisskeyLogo class="misskey"/>-->
|
||||||
|
|||||||
@@ -144,6 +144,19 @@ if (window.innerWidth < 1024) {
|
|||||||
|
|
||||||
document.documentElement.style.overflowY = 'scroll';
|
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(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
isDesktop.value = (window.innerWidth >= DESKTOP_THRESHOLD);
|
isDesktop.value = (window.innerWidth >= DESKTOP_THRESHOLD);
|
||||||
|
|||||||
@@ -178,6 +178,19 @@ 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(() => {
|
onMounted(() => {
|
||||||
if (!isDesktop.value) {
|
if (!isDesktop.value) {
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
|
|||||||
@@ -248,12 +248,12 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||||||
keywords: ['login', 'signin'],
|
keywords: ['login', 'signin'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5RbESWefG',
|
id: 'lUtOQbnwi',
|
||||||
label: i18n.ts._accountSettings.makeNotesFollowersOnlyBefore,
|
label: i18n.ts._accountSettings.makeNotesFollowersOnlyBefore,
|
||||||
keywords: ['follower', i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription],
|
keywords: ['follower', i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hdzwDs3qd',
|
id: '83WWcjwS9',
|
||||||
label: i18n.ts._accountSettings.makeNotesHiddenBefore,
|
label: i18n.ts._accountSettings.makeNotesHiddenBefore,
|
||||||
keywords: ['hidden', i18n.ts._accountSettings.makeNotesHiddenBeforeDescription],
|
keywords: ['hidden', i18n.ts._accountSettings.makeNotesHiddenBeforeDescription],
|
||||||
},
|
},
|
||||||
@@ -791,7 +791,7 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5VSGOVYR0',
|
id: '5VSGOVYR0',
|
||||||
label: i18n.ts._settings.webhook,
|
label: i18n.ts.manage,
|
||||||
keywords: ['webhook'],
|
keywords: ['webhook'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -897,27 +897,22 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||||||
keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
|
keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'b1GYEEJeh',
|
id: '1fV9WINCQ',
|
||||||
label: i18n.ts._settings.makeEveryTextElementsSelectable,
|
|
||||||
keywords: ['text', 'selectable'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'vVLxwINTJ',
|
|
||||||
label: i18n.ts.menuStyle,
|
label: i18n.ts.menuStyle,
|
||||||
keywords: ['menu', 'style', 'popup', 'drawer'],
|
keywords: ['menu', 'style', 'popup', 'drawer'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '14cMhMLHL',
|
id: 'mLQzlKUNu',
|
||||||
label: i18n.ts._contextMenu.title,
|
label: i18n.ts._contextMenu.title,
|
||||||
keywords: ['contextmenu', 'system', 'native'],
|
keywords: ['contextmenu', 'system', 'native'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'oSo4LXMX9',
|
id: 'yP96aA3j9',
|
||||||
label: i18n.ts.fontSize,
|
label: i18n.ts.fontSize,
|
||||||
keywords: ['font', 'size'],
|
keywords: ['font', 'size'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '7LQSAThST',
|
id: 'jQeiMopFE',
|
||||||
label: i18n.ts.useSystemFont,
|
label: i18n.ts.useSystemFont,
|
||||||
keywords: ['font', 'system', 'native'],
|
keywords: ['font', 'system', 'native'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,15 +3,9 @@
|
|||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clipboardに値をコピー(TODO: 文字列以外も対応)
|
* Clipboardに値をコピー(TODO: 文字列以外も対応)
|
||||||
*/
|
*/
|
||||||
export function copyToClipboard(input: string | null) {
|
export function copyToClipboard(input: string | null) {
|
||||||
if (input) {
|
if (input) navigator.clipboard.writeText(input);
|
||||||
navigator.clipboard.writeText(input);
|
|
||||||
os.toast(i18n.ts.copiedToClipboard);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ function toggleSensitive(file: Misskey.entities.DriveFile) {
|
|||||||
|
|
||||||
function copyUrl(file: Misskey.entities.DriveFile) {
|
function copyUrl(file: Misskey.entities.DriveFile) {
|
||||||
copyToClipboard(file.url);
|
copyToClipboard(file.url);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -149,7 +150,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
|||||||
|
|
||||||
if (prefer.s.devMode) {
|
if (prefer.s.devMode) {
|
||||||
menuItems.push({ type: 'divider' }, {
|
menuItems.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyFileId,
|
text: i18n.ts.copyFileId,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(file.id);
|
copyToClipboard(file.id);
|
||||||
|
|||||||
@@ -4,11 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { url } from '@@/js/config.js';
|
|
||||||
import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
|
|
||||||
import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
|
import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
|
||||||
|
import { url } from '@@/js/config.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||||
|
import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
|
||||||
|
|
||||||
const MOBILE_THRESHOLD = 500;
|
const MOBILE_THRESHOLD = 500;
|
||||||
|
|
||||||
@@ -74,6 +74,7 @@ export function genEmbedCode(entity: EmbeddableEntity, id: string, params?: Embe
|
|||||||
// PCじゃない場合はコードカスタマイズ画面を出さずにそのままコピー
|
// PCじゃない場合はコードカスタマイズ画面を出さずにそのままコピー
|
||||||
if (window.innerWidth < MOBILE_THRESHOLD) {
|
if (window.innerWidth < MOBILE_THRESHOLD) {
|
||||||
copyToClipboard(getEmbedCode(`/embed/${entity}/${id}`, _params));
|
copyToClipboard(getEmbedCode(`/embed/${entity}/${id}`, _params));
|
||||||
|
os.success();
|
||||||
} else {
|
} else {
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkEmbedCodeGenDialog.vue')), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkEmbedCodeGenDialog.vue')), {
|
||||||
entity,
|
entity,
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string):
|
|||||||
text,
|
text,
|
||||||
action: (): void => {
|
action: (): void => {
|
||||||
copyToClipboard(`${url}/notes/${note.id}`);
|
copyToClipboard(`${url}/notes/${note.id}`);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -236,6 +237,7 @@ export function getNoteMenu(props: {
|
|||||||
|
|
||||||
function copyContent(): void {
|
function copyContent(): void {
|
||||||
copyToClipboard(appearNote.text);
|
copyToClipboard(appearNote.text);
|
||||||
|
os.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePin(pin: boolean): void {
|
function togglePin(pin: boolean): void {
|
||||||
@@ -322,6 +324,7 @@ export function getNoteMenu(props: {
|
|||||||
text: i18n.ts.copyRemoteLink,
|
text: i18n.ts.copyRemoteLink,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(appearNote.url ?? appearNote.uri);
|
copyToClipboard(appearNote.url ?? appearNote.uri);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-external-link',
|
icon: 'ti ti-external-link',
|
||||||
@@ -480,6 +483,7 @@ export function getNoteMenu(props: {
|
|||||||
text: i18n.ts.copyRemoteLink,
|
text: i18n.ts.copyRemoteLink,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(appearNote.url ?? appearNote.uri);
|
copyToClipboard(appearNote.url ?? appearNote.uri);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-external-link',
|
icon: 'ti ti-external-link',
|
||||||
@@ -508,10 +512,11 @@ export function getNoteMenu(props: {
|
|||||||
|
|
||||||
if (prefer.s.devMode) {
|
if (prefer.s.devMode) {
|
||||||
menuItems.push({ type: 'divider' }, {
|
menuItems.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyNoteId,
|
text: i18n.ts.copyNoteId,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(appearNote.id);
|
copyToClipboard(appearNote.id);
|
||||||
|
os.success();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,7 +401,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
|||||||
|
|
||||||
if (prefer.s.devMode) {
|
if (prefer.s.devMode) {
|
||||||
menuItems.push({ type: 'divider' }, {
|
menuItems.push({ type: 'divider' }, {
|
||||||
icon: 'ti ti-hash',
|
icon: 'ti ti-id',
|
||||||
text: i18n.ts.copyUserId,
|
text: i18n.ts.copyUserId,
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(user.id);
|
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>
|
<MkAvatar v-for="user in users" :key="user.id" :user="user.followee" link preview></MkAvatar>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="$style.bdayFFallback">
|
<div v-else :class="$style.bdayFFallback">
|
||||||
<img :src="infoImageUrl" draggable="false" :class="$style.bdayFFallbackImage"/>
|
<img :src="infoImageUrl" class="_ghost" :class="$style.bdayFFallbackImage"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div class="ekmkgxbj">
|
<div class="ekmkgxbj">
|
||||||
<MkLoading v-if="fetching"/>
|
<MkLoading v-if="fetching"/>
|
||||||
<div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo">
|
<div v-else-if="(!items || items.length === 0) && widgetProps.showHeader" class="_fullinfo">
|
||||||
<img :src="infoImageUrl" draggable="false"/>
|
<img :src="infoImageUrl" class="_ghost"/>
|
||||||
<div>{{ i18n.ts.nothing }}</div>
|
<div>{{ i18n.ts.nothing }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else :class="$style.feed">
|
<div v-else :class="$style.feed">
|
||||||
@@ -25,13 +25,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, computed } from 'vue';
|
import { ref, watch, computed } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
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 { useWidgetPropsManager } from './widget.js';
|
||||||
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
||||||
import type { GetFormResultType } from '@/utility/form.js';
|
import type { GetFormResultType } from '@/utility/form.js';
|
||||||
import MkContainer from '@/components/MkContainer.vue';
|
import MkContainer from '@/components/MkContainer.vue';
|
||||||
|
import { url as base } from '@@/js/config.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
|
|
||||||
const name = 'rss';
|
const name = 'rss';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user