Merge branch 'develop' into instance-silence

This commit is contained in:
Namekuji 2023-04-30 17:52:05 -04:00
commit 2072d41fcd
59 changed files with 1145 additions and 729 deletions

View File

@ -1202,7 +1202,7 @@ _mfm:
inlineMath: "Math (Inline)" inlineMath: "Math (Inline)"
inlineMathDescription: "Display math formulas (KaTeX) in-line" inlineMathDescription: "Display math formulas (KaTeX) in-line"
blockMath: "Math (Block)" blockMath: "Math (Block)"
blockMathDescription: "Display multi-line math formulas (KaTeX) in a block" blockMathDescription: "Display math formulas (KaTeX) in a block"
quote: "Quote" quote: "Quote"
quoteDescription: "Displays content as a quote." quoteDescription: "Displays content as a quote."
emoji: "Custom Emoji" emoji: "Custom Emoji"

View File

@ -1083,7 +1083,7 @@ _mfm:
inlineMath: "数式(インライン)" inlineMath: "数式(インライン)"
inlineMathDescription: "数式(KaTeX)をインラインで表示します。" inlineMathDescription: "数式(KaTeX)をインラインで表示します。"
blockMath: "数式(ブロック)" blockMath: "数式(ブロック)"
blockMathDescription: "複数行の数式(KaTeX)をブロックで表示します。" blockMathDescription: "数式(KaTeX)をブロックで表示します。"
quote: "引用" quote: "引用"
quoteDescription: "内容が引用であることを示せます。" quoteDescription: "内容が引用であることを示せます。"
emoji: "カスタム絵文字" emoji: "カスタム絵文字"
@ -1124,6 +1124,7 @@ _mfm:
rotateDescription: "指定した角度で回転させます。" rotateDescription: "指定した角度で回転させます。"
plain: "プレーン" plain: "プレーン"
plainDescription: "内側の構文を全て無効にします。" plainDescription: "内側の構文を全て無効にします。"
position: 位置
_instanceTicker: _instanceTicker:
none: "表示しない" none: "表示しない"
remote: "リモートユーザーに表示" remote: "リモートユーザーに表示"
@ -1132,7 +1133,7 @@ _serverDisconnectedBehavior:
reload: "自動でリロード" reload: "自動でリロード"
dialog: "ダイアログで警告" dialog: "ダイアログで警告"
quiet: "控えめに警告" quiet: "控えめに警告"
nothing: "何も起こらない" nothing: "何もない"
_channel: _channel:
create: "チャンネルを作成" create: "チャンネルを作成"
edit: "チャンネルを編集" edit: "チャンネルを編集"

View File

@ -1009,9 +1009,9 @@ _mfm:
blockCode: "代码(块)" blockCode: "代码(块)"
blockCodeDescription: "语法高亮显示整块程序代码。" blockCodeDescription: "语法高亮显示整块程序代码。"
inlineMath: "数学公式(内嵌)" inlineMath: "数学公式(内嵌)"
inlineMathDescription: "显示内嵌的KaTex公式。" inlineMathDescription: "显示内嵌的KaTeX公式。"
blockMath: "数学公式(块)" blockMath: "数学公式(块)"
blockMathDescription: "显示整块的多行KaTex数学公式。" blockMathDescription: "显示整块的KaTeX数学公式。"
quote: "引用" quote: "引用"
quoteDescription: "可以用来表示引用的内容。" quoteDescription: "可以用来表示引用的内容。"
emoji: "自定义表情符号" emoji: "自定义表情符号"

View File

@ -1012,9 +1012,9 @@ _mfm:
blockCode: "程式碼(區塊)" blockCode: "程式碼(區塊)"
blockCodeDescription: "在區塊中用高亮度顯示,例如複數行的程式碼語法。" blockCodeDescription: "在區塊中用高亮度顯示,例如複數行的程式碼語法。"
inlineMath: "數學公式(內嵌)" inlineMath: "數學公式(內嵌)"
inlineMathDescription: "顯示內嵌的KaTex數學公式。" inlineMathDescription: "顯示內嵌的KaTeX數學公式。"
blockMath: "數學公式(方塊)" blockMath: "數學公式(方塊)"
blockMathDescription: "以區塊顯示複數行的KaTex數學式。" blockMathDescription: "以區塊顯示KaTeX數學式。"
quote: "引用" quote: "引用"
quoteDescription: "可以用來表示引用的内容。" quoteDescription: "可以用來表示引用的内容。"
emoji: "自訂表情符號" emoji: "自訂表情符號"

View File

@ -40,6 +40,8 @@
"@bull-board/ui": "^4.10.2", "@bull-board/ui": "^4.10.2",
"@napi-rs/cli": "^2.15.0", "@napi-rs/cli": "^2.15.0",
"@tensorflow/tfjs": "^3.21.0", "@tensorflow/tfjs": "^3.21.0",
"focus-trap": "^7.2.0",
"focus-trap-vue": "^4.0.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },

View File

@ -10,7 +10,7 @@ import { renderPerson } from "@/remote/activitypub/renderer/person.js";
import renderEmoji from "@/remote/activitypub/renderer/emoji.js"; import renderEmoji from "@/remote/activitypub/renderer/emoji.js";
import { inbox as processInbox } from "@/queue/index.js"; import { inbox as processInbox } from "@/queue/index.js";
import { isSelfHost, toPuny } from "@/misc/convert-host.js"; import { isSelfHost, toPuny } from "@/misc/convert-host.js";
import { Notes, Users, Emojis, NoteReactions } from "@/models/index.js"; import { Notes, Users, Emojis, NoteReactions, FollowRequests } from "@/models/index.js";
import type { ILocalUser, User } from "@/models/entities/user.js"; import type { ILocalUser, User } from "@/models/entities/user.js";
import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { renderLike } from "@/remote/activitypub/renderer/like.js";
import { getUserKeypair } from "@/misc/keypair-store.js"; import { getUserKeypair } from "@/misc/keypair-store.js";
@ -330,7 +330,7 @@ router.get("/likes/:like", async (ctx) => {
}); });
// follow // follow
router.get("/follows/:follower/:followee", async (ctx) => { router.get("/follows/:follower/:followee", async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req); const verify = await checkFetch(ctx.req);
if (verify !== 200) { if (verify !== 200) {
ctx.status = verify; ctx.status = verify;
@ -365,4 +365,47 @@ router.get("/follows/:follower/:followee", async (ctx) => {
setResponseType(ctx); setResponseType(ctx);
}); });
// follow request
router.get("/follows/:followRequestId", async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
return;
}
const followRequest = await FollowRequests.findOneBy({
id: ctx.params.followRequestId,
});
if (followRequest == null) {
ctx.status = 404;
return;
}
const [follower, followee] = await Promise.all([
Users.findOneBy({
id: followRequest.followerId,
host: IsNull(),
}),
Users.findOneBy({
id: followRequest.followeeId,
host: Not(IsNull()),
}),
]);
if (follower == null || followee == null) {
ctx.status = 404;
return;
}
const meta = await fetchMeta();
if (meta.secureMode || meta.privateMode) {
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
} else {
ctx.set("Cache-Control", "public, max-age=180");
}
ctx.body = renderActivity(renderFollow(follower, followee));
setResponseType(ctx);
});
export default router; export default router;

View File

@ -29,6 +29,7 @@ import {
convertId, convertId,
IdConvertType as IdType, IdConvertType as IdType,
} from "../../../native-utils/built/index.js"; } from "../../../native-utils/built/index.js";
import { convertAttachment } from "./mastodon/converters.js";
// re-export native rust id conversion (function and enum) // re-export native rust id conversion (function and enum)
export { IdType, convertId }; export { IdType, convertId };
@ -93,7 +94,7 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => {
return; return;
} }
const data = await client.uploadMedia(multipartData); const data = await client.uploadMedia(multipartData);
ctx.body = data.data; ctx.body = convertAttachment(data.data as Entity.Attachment);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -112,7 +113,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => {
return; return;
} }
const data = await client.uploadMedia(multipartData); const data = await client.uploadMedia(multipartData);
ctx.body = data.data; ctx.body = convertAttachment(data.data as Entity.Attachment);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;

View File

@ -8,6 +8,8 @@ import { apiTimelineMastodon } from "./endpoints/timeline.js";
import { apiNotificationsMastodon } from "./endpoints/notifications.js"; import { apiNotificationsMastodon } from "./endpoints/notifications.js";
import { apiSearchMastodon } from "./endpoints/search.js"; import { apiSearchMastodon } from "./endpoints/search.js";
import { getInstance } from "./endpoints/meta.js"; import { getInstance } from "./endpoints/meta.js";
import { convertAnnouncement, convertFilter } from "./converters.js";
import { convertId, IdType } from "../index.js";
export function getClient( export function getClient(
BASE_URL: string, BASE_URL: string,
@ -68,7 +70,7 @@ export function apiMastodonCompatible(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getInstanceAnnouncements(); const data = await client.getInstanceAnnouncements();
ctx.body = data.data; ctx.body = data.data.map(announcement => convertAnnouncement(announcement));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -83,7 +85,9 @@ export function apiMastodonCompatible(router: Router): void {
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.dismissInstanceAnnouncement(ctx.params.id); const data = await client.dismissInstanceAnnouncement(
convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -100,7 +104,7 @@ export function apiMastodonCompatible(router: Router): void {
// displayed without being logged in // displayed without being logged in
try { try {
const data = await client.getFilters(); const data = await client.getFilters();
ctx.body = data.data; ctx.body = data.data.map(filter => convertFilter(filter));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;

View File

@ -0,0 +1,44 @@
import { Entity } from "@calckey/megalodon";
import { convertId, IdType } from "../index.js";
function simpleConvert(data: any) {
data.id = convertId(data.id, IdType.MastodonId);
return data;
}
export function convertAccount(account: Entity.Account) { return simpleConvert(account); }
export function convertAnnouncement(announcement: Entity.Announcement) { return simpleConvert(announcement); }
export function convertAttachment(attachment: Entity.Attachment) { return simpleConvert(attachment); }
export function convertFilter(filter: Entity.Filter) { return simpleConvert(filter); }
export function convertList(list: Entity.List) { return simpleConvert(list); }
export function convertNotification(notification: Entity.Notification) {
notification.account = convertAccount(notification.account);
notification.id = convertId(notification.id, IdType.MastodonId);
if (notification.status)
notification.status = convertStatus(notification.status);
return notification;
}
export function convertPoll(poll: Entity.Poll) { return simpleConvert(poll); }
export function convertRelationship(relationship: Entity.Relationship) { return simpleConvert(relationship); }
export function convertStatus(status: Entity.Status) {
status.account = convertAccount(status.account);
status.id = convertId(status.id, IdType.MastodonId);
if (status.in_reply_to_account_id)
status.in_reply_to_account_id = convertId(status.in_reply_to_account_id, IdType.MastodonId);
if (status.in_reply_to_id)
status.in_reply_to_id = convertId(status.in_reply_to_id, IdType.MastodonId);
status.media_attachments = status.media_attachments.map(attachment => convertAttachment(attachment));
status.mentions = status.mentions.map(mention => ({
...mention,
id: convertId(mention.id, IdType.MastodonId),
}));
if (status.poll)
status.poll = convertPoll(status.poll);
if (status.reblog)
status.reblog = convertStatus(status.reblog);
return status;
}

View File

@ -3,8 +3,9 @@ import { resolveUser } from "@/remote/resolve-user.js";
import Router from "@koa/router"; import Router from "@koa/router";
import { FindOptionsWhere, IsNull } from "typeorm"; import { FindOptionsWhere, IsNull } from "typeorm";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { argsToBools, limitToInt } from "./timeline.js"; import { argsToBools, convertTimelinesArgsId, limitToInt } from "./timeline.js";
import { convertId, IdType } from "../../index.js"; import { convertId, IdType } from "../../index.js";
import { convertAccount, convertList, convertRelationship, convertStatus } from "../converters.js";
const relationshipModel = { const relationshipModel = {
id: "", id: "",
@ -62,9 +63,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.updateCredentials( const data = await client.updateCredentials(
(ctx.request as any).body as any, (ctx.request as any).body as any,
); );
let resp = data.data; ctx.body = convertAccount(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -81,9 +80,7 @@ export function apiAccountMastodon(router: Router): void {
(ctx.request.query as any).acct, (ctx.request.query as any).acct,
"accounts", "accounts",
); );
let resp = data.data.accounts[0]; ctx.body = convertAccount(data.data.accounts[0]);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -115,11 +112,7 @@ export function apiAccountMastodon(router: Router): void {
} }
const data = await client.getRelationships(reqIds); const data = await client.getRelationships(reqIds);
let resp = data.data; ctx.body = data.data.map(relationship => convertRelationship(relationship));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
let data = e.response.data; let data = e.response.data;
@ -136,9 +129,7 @@ export function apiAccountMastodon(router: Router): void {
try { try {
const calcId = convertId(ctx.params.id, IdType.CalckeyId); const calcId = convertId(ctx.params.id, IdType.CalckeyId);
const data = await client.getAccount(calcId); const data = await client.getAccount(calcId);
let resp = data.data; ctx.body = convertAccount(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -155,27 +146,9 @@ export function apiAccountMastodon(router: Router): void {
try { try {
const data = await client.getAccountStatuses( const data = await client.getAccountStatuses(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
argsToBools(limitToInt(ctx.query as any)), convertTimelinesArgsId(argsToBools(limitToInt(ctx.query as any))),
); );
let resp = data.data; ctx.body = data.data.map(status => convertStatus(status));
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -193,13 +166,9 @@ export function apiAccountMastodon(router: Router): void {
try { try {
const data = await client.getAccountFollowers( const data = await client.getAccountFollowers(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
limitToInt(ctx.query as any), convertTimelinesArgsId(limitToInt(ctx.query as any)),
); );
let resp = data.data; ctx.body = data.data.map(account => convertAccount(account));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -217,13 +186,9 @@ export function apiAccountMastodon(router: Router): void {
try { try {
const data = await client.getAccountFollowing( const data = await client.getAccountFollowing(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
limitToInt(ctx.query as any), convertTimelinesArgsId(limitToInt(ctx.query as any)),
); );
let resp = data.data; ctx.body = data.data.map(account => convertAccount(account));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -239,8 +204,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountLists(ctx.params.id); const data = await client.getAccountLists(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = data.data.map(list => convertList(list));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -259,9 +226,8 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.followAccount( const data = await client.followAccount(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let acct = data.data; let acct = convertRelationship(data.data);
acct.following = true; acct.following = true;
acct.id = convertId(acct.id, IdType.MastodonId);
ctx.body = acct; ctx.body = acct;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -281,8 +247,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.unfollowAccount( const data = await client.unfollowAccount(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let acct = data.data; let acct = convertRelationship(data.data);
acct.id = convertId(acct.id, IdType.MastodonId);
acct.following = false; acct.following = false;
ctx.body = acct; ctx.body = acct;
} catch (e: any) { } catch (e: any) {
@ -303,9 +268,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.blockAccount( const data = await client.blockAccount(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -324,9 +287,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.unblockAccount( const data = await client.unblockAccount(
convertId(ctx.params.id, IdType.MastodonId), convertId(ctx.params.id, IdType.MastodonId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -346,9 +307,7 @@ export function apiAccountMastodon(router: Router): void {
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
(ctx.request as any).body as any, (ctx.request as any).body as any,
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -367,9 +326,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.unmuteAccount( const data = await client.unmuteAccount(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -384,27 +341,9 @@ export function apiAccountMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = (await client.getBookmarks( const data = (await client.getBookmarks(
limitToInt(ctx.query as any), convertTimelinesArgsId(limitToInt(ctx.query as any)),
)) as any; ));
let resp = data.data; ctx.body = data.data.map(status => convertStatus(status));
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -417,26 +356,8 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getFavourites(limitToInt(ctx.query as any)); const data = await client.getFavourites(convertTimelinesArgsId(limitToInt(ctx.query as any)));
let resp = data.data; ctx.body = data.data.map(status => convertStatus(status));
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -449,12 +370,8 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getMutes(limitToInt(ctx.query as any)); const data = await client.getMutes(convertTimelinesArgsId(limitToInt(ctx.query as any)));
let resp = data.data; ctx.body = data.data.map(account => convertAccount(account));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -467,12 +384,8 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getBlocks(limitToInt(ctx.query as any)); const data = await client.getBlocks(convertTimelinesArgsId(limitToInt(ctx.query as any)));
let resp = data.data; ctx.body = data.data.map(account => convertAccount(account));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -488,11 +401,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.getFollowRequests( const data = await client.getFollowRequests(
((ctx.query as any) || { limit: 20 }).limit, ((ctx.query as any) || { limit: 20 }).limit,
); );
let resp = data.data; ctx.body = data.data.map(account => convertAccount(account));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -510,9 +419,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.acceptFollowRequest( const data = await client.acceptFollowRequest(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -531,9 +438,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.rejectFollowRequest( const data = await client.rejectFollowRequest(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);

View File

@ -1,6 +1,8 @@
import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import megalodon, { MegalodonInterface } from "@calckey/megalodon";
import Router from "@koa/router"; import Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { IdType, convertId } from "../../index.js";
import { convertFilter } from "../converters.js";
export function apiFilterMastodon(router: Router): void { export function apiFilterMastodon(router: Router): void {
router.get("/v1/filters", async (ctx) => { router.get("/v1/filters", async (ctx) => {
@ -10,7 +12,7 @@ export function apiFilterMastodon(router: Router): void {
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.getFilters(); const data = await client.getFilters();
ctx.body = data.data; ctx.body = data.data.map(filter => convertFilter(filter));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -24,8 +26,10 @@ export function apiFilterMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.getFilter(ctx.params.id); const data = await client.getFilter(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertFilter(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -40,7 +44,7 @@ export function apiFilterMastodon(router: Router): void {
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.createFilter(body.phrase, body.context, body); const data = await client.createFilter(body.phrase, body.context, body);
ctx.body = data.data; ctx.body = convertFilter(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -55,11 +59,11 @@ export function apiFilterMastodon(router: Router): void {
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.updateFilter( const data = await client.updateFilter(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
body.phrase, body.phrase,
body.context, body.context,
); );
ctx.body = data.data; ctx.body = convertFilter(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -73,7 +77,9 @@ export function apiFilterMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.deleteFilter(ctx.params.id); const data = await client.deleteFilter(
convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);

View File

@ -1,8 +1,10 @@
import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import megalodon, { MegalodonInterface } from "@calckey/megalodon";
import Router from "@koa/router"; import Router from "@koa/router";
import { koaBody } from "koa-body"; import { koaBody } from "koa-body";
import { convertId, IdType } from "../../index.js";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { toTextWithReaction } from "./timeline.js"; import { convertTimelinesArgsId, toTextWithReaction } from "./timeline.js";
import { convertNotification } from "../converters.js";
function toLimitToInt(q: any) { function toLimitToInt(q: any) {
if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10); if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10);
return q; return q;
@ -15,9 +17,10 @@ export function apiNotificationsMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.getNotifications(toLimitToInt(ctx.query)); const data = await client.getNotifications(convertTimelinesArgsId(toLimitToInt(ctx.query)));
const notfs = data.data; const notfs = data.data;
const ret = notfs.map((n) => { const ret = notfs.map((n) => {
n = convertNotification(n);
if (n.type !== "follow" && n.type !== "follow_request") { if (n.type !== "follow" && n.type !== "follow_request") {
if (n.type === "reaction") n.type = "favourite"; if (n.type === "reaction") n.type = "favourite";
n.status = toTextWithReaction( n.status = toTextWithReaction(
@ -43,8 +46,10 @@ export function apiNotificationsMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const dataRaw = await client.getNotification(ctx.params.id); const dataRaw = await client.getNotification(
const data = dataRaw.data; convertId(ctx.params.id, IdType.CalckeyId)
);
const data = convertNotification(dataRaw.data);
if (data.type !== "follow" && data.type !== "follow_request") { if (data.type !== "follow" && data.type !== "follow_request") {
if (data.type === "reaction") data.type = "favourite"; if (data.type === "reaction") data.type = "favourite";
ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0]; ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0];
@ -79,7 +84,9 @@ export function apiNotificationsMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.dismissNotification(ctx.params.id); const data = await client.dismissNotification(
convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);

View File

@ -3,7 +3,8 @@ import Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import axios from "axios"; import axios from "axios";
import { Converter } from "@calckey/megalodon"; import { Converter } from "@calckey/megalodon";
import { limitToInt } from "./timeline.js"; import { convertTimelinesArgsId, limitToInt } from "./timeline.js";
import { convertAccount, convertStatus } from "../converters.js";
export function apiSearchMastodon(router: Router): void { export function apiSearchMastodon(router: Router): void {
router.get("/v1/search", async (ctx) => { router.get("/v1/search", async (ctx) => {
@ -12,7 +13,7 @@ export function apiSearchMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const query: any = limitToInt(ctx.query); const query: any = convertTimelinesArgsId(limitToInt(ctx.query));
const type = query.type || ""; const type = query.type || "";
const data = await client.search(query.q, type, query); const data = await client.search(query.q, type, query);
ctx.body = data.data; ctx.body = data.data;
@ -27,18 +28,18 @@ export function apiSearchMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const query: any = limitToInt(ctx.query); const query: any = convertTimelinesArgsId(limitToInt(ctx.query));
const type = query.type; const type = query.type;
if (type) { if (type) {
const data = await client.search(query.q, type, query); const data = await client.search(query.q, type, query);
ctx.body = data.data; ctx.body = data.data.accounts.map(account => convertAccount(account));
} else { } else {
const acct = await client.search(query.q, "accounts", query); const acct = await client.search(query.q, "accounts", query);
const stat = await client.search(query.q, "statuses", query); const stat = await client.search(query.q, "statuses", query);
const tags = await client.search(query.q, "hashtags", query); const tags = await client.search(query.q, "hashtags", query);
ctx.body = { ctx.body = {
accounts: acct.data.accounts, accounts: acct.data.accounts.map(account => convertAccount(account)),
statuses: stat.data.statuses, statuses: stat.data.statuses.map(status => convertStatus(status)),
hashtags: tags.data.hashtags, hashtags: tags.data.hashtags,
}; };
} }
@ -57,7 +58,7 @@ export function apiSearchMastodon(router: Router): void {
ctx.request.hostname, ctx.request.hostname,
accessTokens, accessTokens,
); );
ctx.body = data; ctx.body = data.map(status => convertStatus(status));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -69,12 +70,16 @@ export function apiSearchMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
try { try {
const query: any = ctx.query; const query: any = ctx.query;
const data = await getFeaturedUser( let data = await getFeaturedUser(
BASE_URL, BASE_URL,
ctx.request.hostname, ctx.request.hostname,
accessTokens, accessTokens,
query.limit || 20, query.limit || 20,
); );
data = data.map(suggestion => {
suggestion.account = convertAccount(suggestion.account);
return suggestion;
});
console.log(data); console.log(data);
ctx.body = data; ctx.body = data;
} catch (e: any) { } catch (e: any) {

View File

@ -4,7 +4,9 @@ import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
import axios from "axios"; import axios from "axios";
import querystring from "node:querystring"; import querystring from "node:querystring";
import qs from "qs"; import qs from "qs";
import { limitToInt } from "./timeline.js"; import { convertTimelinesArgsId, limitToInt } from "./timeline.js";
import { convertId, IdType } from "../../index.js";
import { convertAccount, convertAttachment, convertPoll, convertStatus } from "../converters.js";
function normalizeQuery(data: any) { function normalizeQuery(data: any) {
const str = querystring.stringify(data); const str = querystring.stringify(data);
@ -18,6 +20,8 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
let body: any = ctx.request.body; let body: any = ctx.request.body;
if (body.in_reply_to_id)
body.in_reply_to_id = convertId(body.in_reply_to_id, IdType.CalckeyId);
if ( if (
(!body.poll && body["poll[options][]"]) || (!body.poll && body["poll[options][]"]) ||
(!body.media_ids && body["media_ids[]"]) (!body.media_ids && body["media_ids[]"])
@ -54,7 +58,7 @@ export function apiStatusMastodon(router: Router): void {
body.sensitive = body.sensitive =
typeof sensitive === "string" ? sensitive === "true" : sensitive; typeof sensitive === "string" ? sensitive === "true" : sensitive;
const data = await client.postStatus(text, body); const data = await client.postStatus(text, body);
ctx.body = data.data; ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -66,8 +70,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getStatus(ctx.params.id); const data = await client.getStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -79,7 +85,9 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.deleteStatus(ctx.params.id); const data = await client.deleteStatus(
convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e.response.data, request.params.id); console.error(e.response.data, request.params.id);
@ -100,10 +108,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const id = ctx.params.id; const id = convertId(ctx.params.id, IdType.CalckeyId);
const data = await client.getStatusContext( const data = await client.getStatusContext(
id, id,
limitToInt(ctx.query as any), convertTimelinesArgsId(limitToInt(ctx.query as any)),
); );
const status = await client.getStatus(id); const status = await client.getStatus(id);
let reqInstance = axios.create({ let reqInstance = axios.create({
@ -126,6 +134,8 @@ export function apiStatusMastodon(router: Router): void {
text, text,
), ),
); );
data.data.ancestors = data.data.ancestors.map(status => convertStatus(status));
data.data.descendants = data.data.descendants.map(status => convertStatus(status));
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -141,8 +151,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getStatusRebloggedBy(ctx.params.id); const data = await client.getStatusRebloggedBy(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = data.data.map(account => convertAccount(account));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -165,11 +177,11 @@ export function apiStatusMastodon(router: Router): void {
const react = await getFirstReaction(BASE_URL, accessTokens); const react = await getFirstReaction(BASE_URL, accessTokens);
try { try {
const a = (await client.createEmojiReaction( const a = (await client.createEmojiReaction(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
react, react,
)) as any; )) as any;
//const data = await client.favouriteStatus(ctx.params.id) as any; //const data = await client.favouriteStatus(ctx.params.id) as any;
ctx.body = a.data; ctx.body = convertStatus(a.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -186,8 +198,11 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const react = await getFirstReaction(BASE_URL, accessTokens); const react = await getFirstReaction(BASE_URL, accessTokens);
try { try {
const data = await client.deleteEmojiReaction(ctx.params.id, react); const data = await client.deleteEmojiReaction(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
react
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -203,8 +218,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.reblogStatus(ctx.params.id); const data = await client.reblogStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -220,8 +237,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.unreblogStatus(ctx.params.id); const data = await client.unreblogStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -237,8 +256,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.bookmarkStatus(ctx.params.id); const data = await client.bookmarkStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -254,8 +275,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = (await client.unbookmarkStatus(ctx.params.id)) as any; const data = await client.unbookmarkStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -271,8 +294,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.pinStatus(ctx.params.id); const data = await client.pinStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -288,8 +313,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.unpinStatus(ctx.params.id); const data = await client.unpinStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -302,8 +329,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getMedia(ctx.params.id); const data = await client.getMedia(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertAttachment(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -316,10 +345,10 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.updateMedia( const data = await client.updateMedia(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
ctx.request.body as any, ctx.request.body as any,
); );
ctx.body = data.data; ctx.body = convertAttachment(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -331,8 +360,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getPoll(ctx.params.id); const data = await client.getPoll(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId)
);
ctx.body = convertPoll(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -347,10 +378,10 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.votePoll( const data = await client.votePoll(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
(ctx.request.body as any).choices, (ctx.request.body as any).choices,
); );
ctx.body = data.data; ctx.body = convertPoll(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;

View File

@ -4,6 +4,8 @@ import { getClient } from "../ApiMastodonCompatibleService.js";
import { statusModel } from "./status.js"; import { statusModel } from "./status.js";
import Autolinker from "autolinker"; import Autolinker from "autolinker";
import { ParsedUrlQuery } from "querystring"; import { ParsedUrlQuery } from "querystring";
import { convertAccount, convertList, convertStatus } from "../converters.js";
import { convertId, IdType } from "../../index.js";
export function limitToInt(q: ParsedUrlQuery) { export function limitToInt(q: ParsedUrlQuery) {
let object: any = q; let object: any = q;
@ -29,6 +31,16 @@ export function argsToBools(q: ParsedUrlQuery) {
return q; return q;
} }
export function convertTimelinesArgsId(q: ParsedUrlQuery) {
if (typeof q.min_id === "string")
q.min_id = convertId(q.min_id, IdType.CalckeyId);
if (typeof q.max_id === "string")
q.max_id = convertId(q.max_id, IdType.CalckeyId);
if (typeof q.since_id === "string")
q.since_id = convertId(q.since_id, IdType.CalckeyId);
return q;
}
export function toTextWithReaction(status: Entity.Status[], host: string) { export function toTextWithReaction(status: Entity.Status[], host: string) {
return status.map((t) => { return status.map((t) => {
if (!t) return statusModel(null, null, [], "no content"); if (!t) return statusModel(null, null, [], "no content");
@ -97,9 +109,10 @@ export function apiTimelineMastodon(router: Router): void {
try { try {
const query: any = ctx.query; const query: any = ctx.query;
const data = query.local const data = query.local
? await client.getLocalTimeline(argsToBools(limitToInt(query))) ? await client.getLocalTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query))))
: await client.getPublicTimeline(argsToBools(limitToInt(query))); : await client.getPublicTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query))));
ctx.body = toTextWithReaction(data.data, ctx.hostname); let resp = data.data.map(status => convertStatus(status));
ctx.body = toTextWithReaction(resp, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -116,9 +129,10 @@ export function apiTimelineMastodon(router: Router): void {
try { try {
const data = await client.getTagTimeline( const data = await client.getTagTimeline(
ctx.params.hashtag, ctx.params.hashtag,
argsToBools(limitToInt(ctx.query)), convertTimelinesArgsId(argsToBools(limitToInt(ctx.query))),
); );
ctx.body = toTextWithReaction(data.data, ctx.hostname); let resp = data.data.map(status => convertStatus(status));
ctx.body = toTextWithReaction(resp, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -132,8 +146,9 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getHomeTimeline(limitToInt(ctx.query)); const data = await client.getHomeTimeline(convertTimelinesArgsId(limitToInt(ctx.query)));
ctx.body = toTextWithReaction(data.data, ctx.hostname); let resp = data.data.map(status => convertStatus(status));
ctx.body = toTextWithReaction(resp, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -149,10 +164,11 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getListTimeline( const data = await client.getListTimeline(
ctx.params.listId, convertId(ctx.params.listId, IdType.CalckeyId),
limitToInt(ctx.query), convertTimelinesArgsId(limitToInt(ctx.query)),
); );
ctx.body = toTextWithReaction(data.data, ctx.hostname); let resp = data.data.map(status => convertStatus(status));
ctx.body = toTextWithReaction(resp, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -166,7 +182,7 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getConversationTimeline(limitToInt(ctx.query)); const data = await client.getConversationTimeline(convertTimelinesArgsId(limitToInt(ctx.query)));
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -181,7 +197,7 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getLists(); const data = await client.getLists();
ctx.body = data.data; ctx.body = data.data.map(list => convertList(list));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -196,8 +212,10 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getList(ctx.params.id); const data = await client.getList(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertList(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -212,7 +230,7 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.createList((ctx.request.body as any).title); const data = await client.createList((ctx.request.body as any).title);
ctx.body = data.data; ctx.body = convertList(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -227,8 +245,11 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.updateList(ctx.params.id, (ctx.request.body as any).title); const data = await client.updateList(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
(ctx.request.body as any).title
);
ctx.body = convertList(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -244,7 +265,9 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.deleteList(ctx.params.id); const data = await client.deleteList(
convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -262,10 +285,10 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountsInList( const data = await client.getAccountsInList(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
ctx.query as any, convertTimelinesArgsId(ctx.query as any),
); );
ctx.body = data.data; ctx.body = data.data.map(account => convertAccount(account));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -282,8 +305,8 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.addAccountsToList( const data = await client.addAccountsToList(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
(ctx.query as any).account_ids, (ctx.query.account_ids as string[]).map(id => convertId(id, IdType.CalckeyId)),
); );
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
@ -302,8 +325,8 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.deleteAccountsFromList( const data = await client.deleteAccountsFromList(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
(ctx.query as any).account_ids, (ctx.query.account_ids as string[]).map(id => convertId(id, IdType.CalckeyId)),
); );
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {

View File

@ -6,6 +6,7 @@ import type { User } from "@/models/entities/user.js";
import { Blockings, FollowRequests, Users } from "@/models/index.js"; import { Blockings, FollowRequests, Users } from "@/models/index.js";
import { genId } from "@/misc/gen-id.js"; import { genId } from "@/misc/gen-id.js";
import { createNotification } from "../../create-notification.js"; import { createNotification } from "../../create-notification.js";
import config from "@/config/index.js";
export default async function ( export default async function (
follower: { follower: {
@ -79,7 +80,7 @@ export default async function (
} }
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
const content = renderActivity(renderFollow(follower, followee)); const content = renderActivity(renderFollow(follower, followee, requestId ?? `${config.url}/follows/${followRequest.id}`));
deliver(follower, content, followee.inbox); deliver(follower, content, followee.inbox);
} }
} }

View File

@ -32,7 +32,7 @@
"autosize": "5.0.2", "autosize": "5.0.2",
"blurhash": "1.1.5", "blurhash": "1.1.5",
"broadcast-channel": "4.19.1", "broadcast-channel": "4.19.1",
"browser-image-resizer": "https://github.com/misskey-dev/browser-image-resizer.git", "browser-image-resizer": "github:misskey-dev/browser-image-resizer",
"calckey-js": "workspace:*", "calckey-js": "workspace:*",
"chart.js": "4.1.1", "chart.js": "4.1.1",
"chartjs-adapter-date-fns": "2.0.1", "chartjs-adapter-date-fns": "2.0.1",

View File

@ -195,8 +195,7 @@ function onMousedown(evt: MouseEvent): void {
} }
&:focus-visible { &:focus-visible {
outline: solid 2px var(--focus); outline: auto;
outline-offset: 2px;
} }
&.inline { &.inline {

View File

@ -1,5 +1,6 @@
<template> <template>
<button <button
ref="el"
class="_button" class="_button"
:class="{ showLess: modelValue, fade: !modelValue }" :class="{ showLess: modelValue, fade: !modelValue }"
@click.stop="toggle" @click.stop="toggle"
@ -12,7 +13,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue"; import { computed, ref } from "vue";
import { length } from "stringz"; import { length } from "stringz";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import { concat } from "@/scripts/array"; import { concat } from "@/scripts/array";
@ -27,6 +28,8 @@ const emit = defineEmits<{
(ev: "update:modelValue", v: boolean): void; (ev: "update:modelValue", v: boolean): void;
}>(); }>();
const el = ref<HTMLElement>();
const label = computed(() => { const label = computed(() => {
return concat([ return concat([
props.note.text props.note.text
@ -43,6 +46,14 @@ const label = computed(() => {
const toggle = () => { const toggle = () => {
emit("update:modelValue", !props.modelValue); emit("update:modelValue", !props.modelValue);
}; };
function focus() {
el.value.focus();
}
defineExpose({
focus,
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -62,9 +73,48 @@ const toggle = () => {
} }
} }
} }
&:hover > span { &:hover > span,
&:focus > span {
background: var(--cwFg) !important; background: var(--cwFg) !important;
color: var(--cwBg) !important; color: var(--cwBg) !important;
} }
&.fade {
display: block;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
z-index: 2;
> span {
display: inline-block;
background: var(--panel);
padding: 0.4em 1em;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
&:hover,
&:focus {
> span {
background: var(--panelHighlight);
}
}
}
&.showLess {
width: 100%;
margin-top: 1em;
position: sticky;
bottom: var(--stickyBottom);
> span {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 0 7px 7px var(--bg);
}
}
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div ref="thumbnail" class="zdjebgpv"> <button ref="thumbnail" class="zdjebgpv">
<ImgWithBlurhash <ImgWithBlurhash
v-if="isThumbnailAvailable" v-if="isThumbnailAvailable"
:hash="file.blurhash" :hash="file.blurhash"
@ -36,7 +36,7 @@
v-if="isThumbnailAvailable && is === 'video'" v-if="isThumbnailAvailable && is === 'video'"
class="ph-file-video ph-bold ph-lg icon-sub" class="ph-file-video ph-bold ph-lg icon-sub"
></i> ></i>
</div> </button>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -88,6 +88,9 @@ const isThumbnailAvailable = computed(() => {
background: var(--panel); background: var(--panel);
border-radius: 8px; border-radius: 8px;
overflow: clip; overflow: clip;
border: 0;
padding: 0;
cursor: pointer;
> .icon-sub { > .icon-sub {
position: absolute; position: absolute;

View File

@ -1,8 +1,10 @@
<template> <template>
<FocusTrap v-bind:active="isActive">
<div <div
class="omfetrab" class="omfetrab"
:class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]"
:style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"
tabindex="-1"
> >
<input <input
ref="search" ref="search"
@ -152,6 +154,7 @@
</button> </button>
</div> </div>
</div> </div>
</FocusTrap>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -171,6 +174,7 @@ import { deviceKind } from "@/scripts/device-kind";
import { emojiCategories, instance } from "@/instance"; import { emojiCategories, instance } from "@/instance";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { FocusTrap } from "focus-trap-vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View File

@ -139,7 +139,8 @@ function close() {
height: 100px; height: 100px;
border-radius: 10px; border-radius: 10px;
&:hover { &:hover,
&:focus-visible {
color: var(--accent); color: var(--accent);
background: var(--accentedBg); background: var(--accentedBg);
text-decoration: none; text-decoration: none;

View File

@ -138,6 +138,10 @@ watch(
background-position: center; background-position: center;
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
box-sizing: border-box;
&:focus-visible {
border: 2px solid var(--accent);
}
> .gif { > .gif {
background-color: var(--fg); background-color: var(--fg);

View File

@ -1,5 +1,5 @@
<template> <template>
<div ref="el" class="sfhdhdhr"> <div ref="el" class="sfhdhdhr" tabindex="-1">
<MkMenu <MkMenu
ref="menu" ref="menu"
:items="items" :items="items"
@ -23,7 +23,6 @@ import {
} from "vue"; } from "vue";
import MkMenu from "./MkMenu.vue"; import MkMenu from "./MkMenu.vue";
import { MenuItem } from "@/types/menu"; import { MenuItem } from "@/types/menu";
import * as os from "@/os";
const props = defineProps<{ const props = defineProps<{
items: MenuItem[]; items: MenuItem[];

View File

@ -1,8 +1,8 @@
<template> <template>
<div> <FocusTrap :active="false" ref="focusTrap">
<div tabindex="-1">
<div <div
ref="itemsEl" ref="itemsEl"
v-hotkey="keymap"
class="rrevdjwt _popup _shadow" class="rrevdjwt _popup _shadow"
:class="{ center: align === 'center', asDrawer }" :class="{ center: align === 'center', asDrawer }"
:style="{ :style="{
@ -14,11 +14,12 @@
<template v-for="(item, i) in items2"> <template v-for="(item, i) in items2">
<div v-if="item === null" class="divider"></div> <div v-if="item === null" class="divider"></div>
<span v-else-if="item.type === 'label'" class="label item"> <span v-else-if="item.type === 'label'" class="label item">
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
</span> </span>
<span <span
v-else-if="item.type === 'pending'" v-else-if="item.type === 'pending'"
:tabindex="i"
class="pending item" class="pending item"
> >
<span><MkEllipsis /></span> <span><MkEllipsis /></span>
@ -26,7 +27,6 @@
<MkA <MkA
v-else-if="item.type === 'link'" v-else-if="item.type === 'link'"
:to="item.to" :to="item.to"
:tabindex="i"
class="_button item" class="_button item"
@click.passive="close(true)" @click.passive="close(true)"
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@ -48,8 +48,11 @@
v-if="item.avatar" v-if="item.avatar"
:user="item.avatar" :user="item.avatar"
class="avatar" class="avatar"
disableLink
/> />
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
<span v-if="item.indicate" class="indicator" <span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i ><i class="ph-circle ph-fill"></i
></span> ></span>
@ -59,7 +62,6 @@
:href="item.href" :href="item.href"
:target="item.target" :target="item.target"
:download="item.download" :download="item.download"
:tabindex="i"
class="_button item" class="_button item"
@click="close(true)" @click="close(true)"
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@ -77,14 +79,15 @@
:class="icon" :class="icon"
></i> ></i>
</span> </span>
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
<span v-if="item.indicate" class="indicator" <span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i ><i class="ph-circle ph-fill"></i
></span> ></span>
</a> </a>
<button <button
v-else-if="item.type === 'user' && !items.hidden" v-else-if="item.type === 'user' && !items.hidden"
:tabindex="i"
class="_button item" class="_button item"
:class="{ active: item.active }" :class="{ active: item.active }"
:disabled="item.active" :disabled="item.active"
@ -92,16 +95,17 @@
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)" @mouseleave.passive="onItemMouseLeave(item)"
> >
<MkAvatar :user="item.user" class="avatar" /><MkUserName <MkAvatar
:user="item.user" :user="item.user"
/> class="avatar"
disableLink
/><MkUserName :user="item.user" />
<span v-if="item.indicate" class="indicator" <span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i ><i class="ph-circle ph-fill"></i
></span> ></span>
</button> </button>
<span <span
v-else-if="item.type === 'switch'" v-else-if="item.type === 'switch'"
:tabindex="i"
class="item" class="item"
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)" @mouseleave.passive="onItemMouseLeave(item)"
@ -116,10 +120,10 @@
</span> </span>
<button <button
v-else-if="item.type === 'parent'" v-else-if="item.type === 'parent'"
:tabindex="i"
class="_button item parent" class="_button item parent"
:class="{ childShowing: childShowingItem === item }" :class="{ childShowing: childShowingItem === item }"
@mouseenter="showChildren(item, $event)" @mouseenter="showChildren(item, $event)"
@click="showChildren(item, $event)"
> >
<i <i
v-if="item.icon" v-if="item.icon"
@ -133,14 +137,17 @@
:class="icon" :class="icon"
></i> ></i>
</span> </span>
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
<span class="caret" <span class="caret"
><i class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"></i ><i
class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"
></i
></span> ></span>
</button> </button>
<button <button
v-else-if="!item.hidden" v-else-if="!item.hidden"
:tabindex="i"
class="_button item" class="_button item"
:class="{ danger: item.danger, active: item.active }" :class="{ danger: item.danger, active: item.active }"
:disabled="item.active" :disabled="item.active"
@ -164,8 +171,11 @@
v-if="item.avatar" v-if="item.avatar"
:user="item.avatar" :user="item.avatar"
class="avatar" class="avatar"
disableLink
/> />
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
<span v-if="item.indicate" class="indicator" <span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i ><i class="ph-circle ph-fill"></i
></span> ></span>
@ -186,6 +196,7 @@
/> />
</div> </div>
</div> </div>
</FocusTrap>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -206,8 +217,10 @@ import FormSwitch from "@/components/form/switch.vue";
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu"; import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { FocusTrap } from "focus-trap-vue";
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue")); const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
const focusTrap = ref();
const props = defineProps<{ const props = defineProps<{
items: MenuItem[]; items: MenuItem[];
@ -228,12 +241,6 @@ let items2: InnerMenuItem[] = $ref([]);
let child = $ref<InstanceType<typeof XChild>>(); let child = $ref<InstanceType<typeof XChild>>();
let keymap = computed(() => ({
"up|k|shift+tab": focusUp,
"down|j|tab": focusDown,
esc: close,
}));
let childShowingItem = $ref<MenuItem | null>(); let childShowingItem = $ref<MenuItem | null>();
watch( watch(
@ -324,6 +331,8 @@ function focusDown() {
} }
onMounted(() => { onMounted(() => {
focusTrap.value.activate();
if (props.viaKeyboard) { if (props.viaKeyboard) {
nextTick(() => { nextTick(() => {
focusNext(itemsEl.children[0], true, false); focusNext(itemsEl.children[0], true, false);
@ -364,8 +373,7 @@ onBeforeUnmount(() => {
font-size: 0.9em; font-size: 0.9em;
line-height: 20px; line-height: 20px;
text-align: left; text-align: left;
overflow: hidden; outline: none;
text-overflow: ellipsis;
&:before { &:before {
content: ""; content: "";
@ -389,7 +397,8 @@ onBeforeUnmount(() => {
transform: translateY(0em); transform: translateY(0em);
} }
&:not(:disabled):hover { &:not(:disabled):hover,
&:focus-visible {
color: var(--accent); color: var(--accent);
text-decoration: none; text-decoration: none;
@ -397,6 +406,9 @@ onBeforeUnmount(() => {
background: var(--accentedBg); background: var(--accentedBg);
} }
} }
&:focus-visible:before {
outline: auto;
}
&.danger { &.danger {
color: #eb6f92; color: #eb6f92;

View File

@ -14,9 +14,11 @@
:duration="transitionDuration" :duration="transitionDuration"
appear appear
@after-leave="emit('closed')" @after-leave="emit('closed')"
@keyup.esc="emit('click')"
@enter="emit('opening')" @enter="emit('opening')"
@after-enter="onOpened" @after-enter="onOpened"
> >
<FocusTrap v-model:active="isActive">
<div <div
v-show="manualShowing != null ? manualShowing : showing" v-show="manualShowing != null ? manualShowing : showing"
v-hotkey.global="keymap" v-hotkey.global="keymap"
@ -24,17 +26,22 @@
$style.root, $style.root,
{ {
[$style.drawer]: type === 'drawer', [$style.drawer]: type === 'drawer',
[$style.dialog]: type === 'dialog' || type === 'dialog:top', [$style.dialog]:
type === 'dialog' || type === 'dialog:top',
[$style.popup]: type === 'popup', [$style.popup]: type === 'popup',
}, },
]" ]"
:style="{ :style="{
zIndex, zIndex,
pointerEvents: (manualShowing != null ? manualShowing : showing) pointerEvents: (
manualShowing != null ? manualShowing : showing
)
? 'auto' ? 'auto'
: 'none', : 'none',
'--transformOrigin': transformOrigin, '--transformOrigin': transformOrigin,
}" }"
tabindex="-1"
v-focus
> >
<div <div
class="_modalBg data-cy-bg" class="_modalBg data-cy-bg"
@ -62,6 +69,7 @@
<slot :max-height="maxHeight" :type="type"></slot> <slot :max-height="maxHeight" :type="type"></slot>
</div> </div>
</div> </div>
</FocusTrap>
</Transition> </Transition>
</template> </template>
@ -71,6 +79,7 @@ import * as os from "@/os";
import { isTouchUsing } from "@/scripts/touch"; import { isTouchUsing } from "@/scripts/touch";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { deviceKind } from "@/scripts/device-kind"; import { deviceKind } from "@/scripts/device-kind";
import { FocusTrap } from "focus-trap-vue";
function getFixedContainer(el: Element | null): Element | null { function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === "BODY") return null; if (el == null || el.tagName === "BODY") return null;
@ -166,6 +175,7 @@ let transitionDuration = $computed(() =>
let contentClicking = false; let contentClicking = false;
const focusedElement = document.activeElement;
function close(opts: { useSendAnimation?: boolean } = {}) { function close(opts: { useSendAnimation?: boolean } = {}) {
if (opts.useSendAnimation) { if (opts.useSendAnimation) {
useSendAnime = true; useSendAnime = true;
@ -175,10 +185,12 @@ function close(opts: { useSendAnimation?: boolean } = {}) {
if (props.src) props.src.style.pointerEvents = "auto"; if (props.src) props.src.style.pointerEvents = "auto";
showing = false; showing = false;
emit("close"); emit("close");
focusedElement.focus();
} }
function onBgClick() { function onBgClick() {
if (contentClicking) return; if (contentClicking) return;
focusedElement.focus();
emit("click"); emit("click");
} }
@ -481,6 +493,7 @@ defineExpose({
} }
.root { .root {
outline: none;
&.dialog { &.dialog {
> .content { > .content {
position: fixed; position: fixed;

View File

@ -158,6 +158,7 @@ function onContextmenu(ev: MouseEvent) {
flex-direction: column; flex-direction: column;
contain: content; contain: content;
border-radius: var(--radius); border-radius: var(--radius);
margin: auto;
--root-margin: 24px; --root-margin: 24px;

View File

@ -3,8 +3,10 @@
ref="modal" ref="modal"
:prefer-type="'dialog'" :prefer-type="'dialog'"
@click="onBgClick" @click="onBgClick"
@keyup.esc="$emit('close')"
@closed="$emit('closed')" @closed="$emit('closed')"
> >
<FocusTrap v-model:active="isActive">
<div <div
ref="rootEl" ref="rootEl"
class="ebkgoccj" class="ebkgoccj"
@ -19,6 +21,7 @@
: '100%', : '100%',
}" }"
@keydown="onKeydown" @keydown="onKeydown"
tabindex="-1"
> >
<div ref="headerEl" class="header"> <div ref="headerEl" class="header">
<button <button
@ -51,11 +54,13 @@
<slot :width="bodyWidth" :height="bodyHeight"></slot> <slot :width="bodyWidth" :height="bodyHeight"></slot>
</div> </div>
</div> </div>
</FocusTrap>
</MkModal> </MkModal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted } from "vue"; import { onMounted, onUnmounted } from "vue";
import { FocusTrap } from "focus-trap-vue";
import MkModal from "./MkModal.vue"; import MkModal from "./MkModal.vue";
const props = withDefaults( const props = withDefaults(

View File

@ -84,6 +84,7 @@
:detailedView="detailedView" :detailedView="detailedView"
:parentId="appearNote.parentId" :parentId="appearNote.parentId"
@push="(e) => router.push(notePage(e))" @push="(e) => router.push(notePage(e))"
@focusfooter="footerEl.focus()"
></MkSubNoteContent> ></MkSubNoteContent>
<div v-if="translating || translation" class="translation"> <div v-if="translating || translation" class="translation">
<MkLoading v-if="translating" mini /> <MkLoading v-if="translating" mini />
@ -117,7 +118,7 @@
<MkTime :time="appearNote.createdAt" mode="absolute" /> <MkTime :time="appearNote.createdAt" mode="absolute" />
</MkA> </MkA>
</div> </div>
<footer ref="el" class="footer" @click.stop> <footer ref="footerEl" class="footer" @click.stop tabindex="-1">
<XReactionsViewer <XReactionsViewer
v-if="enableEmojiReactions" v-if="enableEmojiReactions"
ref="reactionsViewer" ref="reactionsViewer"
@ -278,6 +279,7 @@ const isRenote =
note.poll == null; note.poll == null;
const el = ref<HTMLElement>(); const el = ref<HTMLElement>();
const footerEl = ref<HTMLElement>();
const menuButton = ref<HTMLElement>(); const menuButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton>>(); const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
@ -298,8 +300,8 @@ const keymap = {
r: () => reply(true), r: () => reply(true),
"e|a|plus": () => react(true), "e|a|plus": () => react(true),
q: () => renoteButton.value.renote(true), q: () => renoteButton.value.renote(true),
"up|k|shift+tab": focusBefore, "up|k": focusBefore,
"down|j|tab": focusAfter, "down|j": focusAfter,
esc: blur, esc: blur,
"m|o": () => menu(true), "m|o": () => menu(true),
s: () => showContent.value !== showContent.value, s: () => showContent.value !== showContent.value,

View File

@ -1,6 +1,6 @@
<template> <template>
<div v-size="{ min: [350, 500] }" class="fefdfafb"> <div v-size="{ min: [350, 500] }" class="fefdfafb">
<MkAvatar class="avatar" :user="$i" /> <MkAvatar class="avatar" :user="$i" disableLink />
<div class="main"> <div class="main">
<div class="header"> <div class="header">
<MkUserName :user="$i" /> <MkUserName :user="$i" />

View File

@ -26,6 +26,7 @@
:note="note" :note="note"
:parentId="appearNote.parentId" :parentId="appearNote.parentId"
:conversation="conversation" :conversation="conversation"
@focusfooter="footerEl.focus()"
/> />
<div v-if="translating || translation" class="translation"> <div v-if="translating || translation" class="translation">
<MkLoading v-if="translating" mini /> <MkLoading v-if="translating" mini />
@ -46,7 +47,7 @@
</div> </div>
</div> </div>
</div> </div>
<footer class="footer" @click.stop> <footer ref="footerEl" class="footer" @click.stop tabindex="-1">
<XReactionsViewer <XReactionsViewer
v-if="enableEmojiReactions" v-if="enableEmojiReactions"
ref="reactionsViewer" ref="reactionsViewer"
@ -212,6 +213,7 @@ const isRenote =
note.poll == null; note.poll == null;
const el = ref<HTMLElement>(); const el = ref<HTMLElement>();
const footerEl = ref<HTMLElement>();
const menuButton = ref<HTMLElement>(); const menuButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton>>(); const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();

View File

@ -7,6 +7,8 @@
:transparent-bg="true" :transparent-bg="true"
@click="modal.close()" @click="modal.close()"
@closed="emit('closed')" @closed="emit('closed')"
tabindex="-1"
v-focus
> >
<MkMenu <MkMenu
:items="items" :items="items"

View File

@ -198,7 +198,6 @@ export default defineComponent({
height: 64px; height: 64px;
margin-right: 4px; margin-right: 4px;
border-radius: 4px; border-radius: 4px;
overflow: hidden;
cursor: move; cursor: move;
&:hover > .remove { &:hover > .remove {

View File

@ -35,7 +35,20 @@
class="content" class="content"
:class="{ collapsed, isLong, showContent: note.cw && !showContent }" :class="{ collapsed, isLong, showContent: note.cw && !showContent }"
> >
<div class="body"> <XCwButton
ref="cwButton"
v-if="note.cw && !showContent"
v-model="showContent"
:note="note"
v-on:keydown="focusFooter"
/>
<div
class="body"
v-bind="{
'aria-label': !showContent ? '' : null,
tabindex: !showContent ? '-1' : null,
}"
>
<span v-if="note.deletedAt" style="opacity: 0.5" <span v-if="note.deletedAt" style="opacity: 0.5"
>({{ i18n.ts.deleted }})</span >({{ i18n.ts.deleted }})</span
> >
@ -96,28 +109,27 @@
<XNoteSimple :note="note.renote" /> <XNoteSimple :note="note.renote" />
</div> </div>
</template> </template>
<div
v-if="note.cw && !showContent"
tabindex="0"
v-on:focus="cwButton?.focus()"
></div>
</div> </div>
<button <XShowMoreButton
v-if="isLong && collapsed" v-if="isLong"
class="fade _button" v-model="collapsed"
@click.stop="collapsed = false" ></XShowMoreButton>
> <XCwButton
<span>{{ i18n.ts.showMore }}</span> v-if="note.cw && showContent"
</button> v-model="showContent"
<button :note="note"
v-if="isLong && !collapsed" />
class="showLess _button"
@click.stop="collapsed = true"
>
<span>{{ i18n.ts.showLess }}</span>
</button>
<XCwButton v-if="note.cw" v-model="showContent" :note="note" />
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import { ref } from "vue";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import XNoteSimple from "@/components/MkNoteSimple.vue"; import XNoteSimple from "@/components/MkNoteSimple.vue";
@ -138,8 +150,10 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "push", v): void; (ev: "push", v): void;
(ev: "focusfooter"): void;
}>(); }>();
const cwButton = ref<HTMLElement>();
const isLong = const isLong =
!props.detailedView && !props.detailedView &&
props.note.cw == null && props.note.cw == null &&
@ -151,6 +165,12 @@ const urls = props.note.text
: null; : null;
let showContent = $ref(false); let showContent = $ref(false);
function focusFooter(ev) {
if (ev.key == "Tab" && !ev.getModifierState("Shift")) {
emit("focusfooter");
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -242,6 +262,9 @@ let showContent = $ref(false);
margin-top: -50px; margin-top: -50px;
padding-top: 50px; padding-top: 50px;
overflow: hidden; overflow: hidden;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
} }
&.collapsed > .body { &.collapsed > .body {
box-sizing: border-box; box-sizing: border-box;

View File

@ -9,7 +9,6 @@
v-if="item.type === 'a'" v-if="item.type === 'a'"
:href="item.href" :href="item.href"
:target="item.target" :target="item.target"
:tabindex="i"
class="_button item" class="_button item"
:class="{ danger: item.danger, active: item.active }" :class="{ danger: item.danger, active: item.active }"
> >
@ -22,7 +21,6 @@
</a> </a>
<button <button
v-else-if="item.type === 'button'" v-else-if="item.type === 'button'"
:tabindex="i"
class="_button item" class="_button item"
:class="{ danger: item.danger, active: item.active }" :class="{ danger: item.danger, active: item.active }"
:disabled="item.active" :disabled="item.active"
@ -38,7 +36,6 @@
<MkA <MkA
v-else v-else
:to="item.to" :to="item.to"
:tabindex="i"
class="_button item" class="_button item"
:class="{ danger: item.danger, active: item.active }" :class="{ danger: item.danger, active: item.active }"
> >
@ -99,7 +96,8 @@ export default defineComponent({
font-size: 0.9em; font-size: 0.9em;
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
&:hover { &:hover,
&:focus-visible {
text-decoration: none; text-decoration: none;
background: var(--panelHighlight); background: var(--panelHighlight);
} }

View File

@ -46,7 +46,10 @@
/></MkA> /></MkA>
<p class="username"><MkAcct :user="user" /></p> <p class="username"><MkAcct :user="user" /></p>
</div> </div>
<div class="description" :class="{ collapsed: isLong && collapsed }"> <div
class="description"
:class="{ collapsed: isLong && collapsed }"
>
<Mfm <Mfm
v-if="user.description" v-if="user.description"
:text="user.description" :text="user.description"
@ -149,14 +152,15 @@ let user = $ref<misskey.entities.UserDetailed | null>(null);
let top = $ref(0); let top = $ref(0);
let left = $ref(0); let left = $ref(0);
let isLong = $ref(false); let isLong = $ref(false);
let collapsed = $ref(!isLong); let collapsed = $ref(!isLong);
onMounted(() => { onMounted(() => {
if (typeof props.q === "object") { if (typeof props.q === "object") {
user = props.q; user = props.q;
isLong = (user.description.split("\n").length > 9 || user.description.length > 400); isLong =
user.description.split("\n").length > 9 ||
user.description.length > 400;
} else { } else {
const query = props.q.startsWith("@") const query = props.q.startsWith("@")
? Acct.parse(props.q.substr(1)) ? Acct.parse(props.q.substr(1))
@ -165,11 +169,12 @@ onMounted(() => {
os.api("users/show", query).then((res) => { os.api("users/show", query).then((res) => {
if (!props.showing) return; if (!props.showing) return;
user = res; user = res;
isLong = (user.description.split("\n").length > 9 || user.description.length > 400); isLong =
user.description.split("\n").length > 9 ||
user.description.length > 400;
}); });
} }
const rect = props.source.getBoundingClientRect(); const rect = props.source.getBoundingClientRect();
const x = const x =
rect.left + props.source.offsetWidth / 2 - 300 / 2 + window.pageXOffset; rect.left + props.source.offsetWidth / 2 - 300 / 2 + window.pageXOffset;
@ -313,7 +318,7 @@ onMounted(() => {
> .fields { > .fields {
padding: 0 16px; padding: 0 16px;
font-size: .8em; font-size: 0.8em;
margin-top: 1em; margin-top: 1em;
> .field { > .field {

View File

@ -46,6 +46,7 @@
:user="user" :user="user"
class="avatar" class="avatar"
:show-indicator="true" :show-indicator="true"
disableLink
/> />
<div class="body"> <div class="body">
<MkUserName :user="user" class="name" /> <MkUserName :user="user" class="name" />
@ -73,6 +74,7 @@
:user="user" :user="user"
class="avatar" class="avatar"
:show-indicator="true" :show-indicator="true"
disableLink
/> />
<div class="body"> <div class="body">
<MkUserName :user="user" class="name" /> <MkUserName :user="user" class="name" />

View File

@ -7,7 +7,7 @@
> >
<div class="beaffaef"> <div class="beaffaef">
<div v-for="u in users" :key="u.id" class="user"> <div v-for="u in users" :key="u.id" class="user">
<MkAvatar class="avatar" :user="u" /> <MkAvatar class="avatar" :user="u" disableLink />
<MkUserName class="name" :user="u" :nowrap="true" /> <MkUserName class="name" :user="u" :nowrap="true" />
</div> </div>
<div v-if="users.length < count" class="omitted"> <div v-if="users.length < count" class="omitted">

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="vjoppmmu"> <div class="vjoppmmu">
<template v-if="edit"> <template v-if="edit">
<header> <header tabindex="-1" v-focus>
<MkSelect <MkSelect
v-model="widgetAdderSelected" v-model="widgetAdderSelected"
style="margin-bottom: var(--margin)" style="margin-bottom: var(--margin)"

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="dwzlatin" :class="{ opened }"> <div class="dwzlatin" :class="{ opened }">
<div class="header _button" @click="toggle"> <button class="header _button" @click="toggle">
<span class="icon"><slot name="icon"></slot></span> <span class="icon"><slot name="icon"></slot></span>
<span class="text"><slot name="label"></slot></span> <span class="text"><slot name="label"></slot></span>
<span class="right"> <span class="right">
@ -8,7 +8,7 @@
<i v-if="opened" class="ph-caret-up ph-bold ph-lg icon"></i> <i v-if="opened" class="ph-caret-up ph-bold ph-lg icon"></i>
<i v-else class="ph-caret-down ph-bold ph-lg icon"></i> <i v-else class="ph-caret-down ph-bold ph-lg icon"></i>
</span> </span>
</div> </button>
<KeepAlive> <KeepAlive>
<div v-if="openedAtLeastOnce" v-show="opened" class="body"> <div v-if="openedAtLeastOnce" v-show="opened" class="body">
<MkSpacer :margin-min="14" :margin-max="22"> <MkSpacer :margin-min="14" :margin-max="22">

View File

@ -66,6 +66,9 @@ function toggle(): void {
&:hover { &:hover {
border-color: var(--inputBorderHover) !important; border-color: var(--inputBorderHover) !important;
} }
&:focus-within {
outline: auto;
}
&.checked { &.checked {
background-color: var(--accentedBg) !important; background-color: var(--accentedBg) !important;

View File

@ -99,6 +99,9 @@ const toggle = () => {
border-color: var(--inputBorderHover) !important; border-color: var(--inputBorderHover) !important;
} }
} }
&:focus-within > .button {
outline: auto;
}
> .label { > .label {
margin-left: 12px; margin-left: 12px;

View File

@ -19,6 +19,7 @@
class="avatar" class="avatar"
:user="$i" :user="$i"
:disable-preview="true" :disable-preview="true"
disableLink
/> />
</div> </div>
<template v-if="metadata"> <template v-if="metadata">
@ -33,6 +34,7 @@
:user="metadata.avatar" :user="metadata.avatar"
:disable-preview="true" :disable-preview="true"
:show-indicator="true" :show-indicator="true"
disableLink
/> />
<i <i
v-else-if="metadata.icon && !narrow" v-else-if="metadata.icon && !narrow"

View File

@ -5,6 +5,9 @@
:is="currentPageComponent" :is="currentPageComponent"
:key="key" :key="key"
v-bind="Object.fromEntries(currentPageProps)" v-bind="Object.fromEntries(currentPageProps)"
tabindex="-1"
v-focus
style="outline: none"
/> />
<template #fallback> <template #fallback>

View File

@ -0,0 +1,3 @@
export default {
mounted: (el) => el.focus()
}

View File

@ -11,6 +11,7 @@ import anim from "./anim";
import clickAnime from "./click-anime"; import clickAnime from "./click-anime";
import panel from "./panel"; import panel from "./panel";
import adaptiveBorder from "./adaptive-border"; import adaptiveBorder from "./adaptive-border";
import focus from "./focus";
export default function (app: App) { export default function (app: App) {
app.directive("userPreview", userPreview); app.directive("userPreview", userPreview);
@ -25,4 +26,5 @@ export default function (app: App) {
app.directive("click-anime", clickAnime); app.directive("click-anime", clickAnime);
app.directive("panel", panel); app.directive("panel", panel);
app.directive("adaptive-border", adaptiveBorder); app.directive("adaptive-border", adaptiveBorder);
app.directive("focus", focus);
} }

View File

@ -76,23 +76,32 @@ export default {
ev.preventDefault(); ev.preventDefault();
}); });
el.addEventListener( function showTooltip() {
start,
() => {
window.clearTimeout(self.showTimer); window.clearTimeout(self.showTimer);
window.clearTimeout(self.hideTimer); window.clearTimeout(self.hideTimer);
self.showTimer = window.setTimeout(self.show, delay); self.showTimer = window.setTimeout(self.show, delay);
}, }
function hideTooltip() {
window.clearTimeout(self.showTimer);
window.clearTimeout(self.hideTimer);
self.hideTimer = window.setTimeout(self.close, delay);
}
el.addEventListener(
start, showTooltip,
{ passive: true },
);
el.addEventListener(
"focusin", showTooltip,
{ passive: true }, { passive: true },
); );
el.addEventListener( el.addEventListener(
end, end, hideTooltip,
() => { { passive: true },
window.clearTimeout(self.showTimer); );
window.clearTimeout(self.hideTimer); el.addEventListener(
self.hideTimer = window.setTimeout(self.close, delay); "focusout", hideTooltip,
},
{ passive: true }, { passive: true },
); );

View File

@ -313,10 +313,8 @@ onUnmounted(() => {
font-weight: normal; font-weight: normal;
opacity: 0.7; opacity: 0.7;
&:hover { &:hover,
opacity: 1; &:focus-visible,
}
&.active { &.active {
opacity: 1; opacity: 1;
} }

View File

@ -12,7 +12,12 @@
class="user" class="user"
:to="`/user-info/${user.id}`" :to="`/user-info/${user.id}`"
> >
<MkAvatar :user="user" class="avatar" indicator /> <MkAvatar
:user="user"
class="avatar"
indicator
disableLink
/>
</MkA> </MkA>
</div> </div>
</Transition> </Transition>

View File

@ -23,6 +23,7 @@
class="avatar" class="avatar"
:user="req.follower" :user="req.follower"
:show-indicator="true" :show-indicator="true"
disableLink
/> />
<div class="body"> <div class="body">
<div class="name"> <div class="name">

View File

@ -2,7 +2,7 @@
<MkStickyContainer> <MkStickyContainer>
<template #header><MkPageHeader /></template> <template #header><MkPageHeader /></template>
<MkSpacer :content-max="800"> <MkSpacer :content-max="800">
<div class="mwysmxbg"> <div :class="$style.root">
<div>{{ i18n.ts._mfm.intro }}</div> <div>{{ i18n.ts._mfm.intro }}</div>
<br /> <br />
<div class="section _block"> <div class="section _block">
@ -137,6 +137,18 @@
</div> </div>
</div> </div>
</div> </div>
<div class="section _block">
<div class="title">{{ i18n.ts._mfm.blockMath }}</div>
<div class="content">
<p>{{ i18n.ts._mfm.blockMathDescription }}</p>
<div class="preview">
<Mfm :text="preview_blockMath" />
<MkTextarea v-model="preview_blockMath"
><template #label>MFM</template></MkTextarea
>
</div>
</div>
</div>
<!-- deprecated <!-- deprecated
<div class="section _block"> <div class="section _block">
<div class="title">{{ i18n.ts._mfm.search }}</div> <div class="title">{{ i18n.ts._mfm.search }}</div>
@ -427,8 +439,11 @@ let preview_blockCode = $ref(
'```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```' '```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```'
); );
let preview_inlineMath = $ref("\\(x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\)"); let preview_inlineMath = $ref("\\(x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\)");
let preview_blockMath = $ref("\\[x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\]");
let preview_quote = $ref(`> ${i18n.ts._mfm.dummy}`); let preview_quote = $ref(`> ${i18n.ts._mfm.dummy}`);
let preview_search = $ref(`${i18n.ts._mfm.dummy} 検索`); let preview_search = $ref(
`${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [検索]\n${i18n.ts._mfm.dummy} 検索`
);
let preview_jelly = $ref("$[jelly 🍮] $[jelly.speed=5s 🍮]"); let preview_jelly = $ref("$[jelly 🍮] $[jelly.speed=5s 🍮]");
let preview_tada = $ref("$[tada 🍮] $[tada.speed=5s 🍮]"); let preview_tada = $ref("$[tada 🍮] $[tada.speed=5s 🍮]");
let preview_jump = $ref("$[jump 🍮] $[jump.speed=5s 🍮]"); let preview_jump = $ref("$[jump 🍮] $[jump.speed=5s 🍮]");
@ -450,9 +465,15 @@ let preview_x4 = $ref("$[x4 🍮]");
let preview_blur = $ref(`$[blur ${i18n.ts._mfm.dummy}]`); let preview_blur = $ref(`$[blur ${i18n.ts._mfm.dummy}]`);
let preview_rainbow = $ref("$[rainbow 🍮] $[rainbow.speed=5s 🍮]"); let preview_rainbow = $ref("$[rainbow 🍮] $[rainbow.speed=5s 🍮]");
let preview_sparkle = $ref("$[sparkle 🍮]"); let preview_sparkle = $ref("$[sparkle 🍮]");
let preview_rotate = $ref("$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]"); let preview_rotate = $ref(
let preview_position = $ref("$[position.y=-1 Positioning]\n$[position.x=-1 Positioning]"); "$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]"
let preview_scale = $ref("$[scale.x=1.3 Scaling]\n$[scale.x=1.3,y=2 Scaling]\n$[scale.y=0.3 Tiny scaling]"); );
let preview_position = $ref(
"$[position.y=-1 Positioning]\n$[position.x=-1 Positioning]"
);
let preview_scale = $ref(
"$[scale.x=1.3 Scaling]\n$[scale.x=1.3,y=2 Scaling]\n$[scale.y=0.3 Tiny scaling]"
);
let preview_fg = $ref("$[fg.color=ff0000 Text color]"); let preview_fg = $ref("$[fg.color=ff0000 Text color]");
let preview_bg = $ref("$[bg.color=ff0000 Background color]"); let preview_bg = $ref("$[bg.color=ff0000 Background color]");
let preview_plain = $ref( let preview_plain = $ref(
@ -465,8 +486,8 @@ definePageMetadata({
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.mwysmxbg { .root {
background: var(--bg); background: var(--bg);
> .section { > .section {

View File

@ -6,14 +6,14 @@
{{ i18n.ts.addAccount }}</FormButton {{ i18n.ts.addAccount }}</FormButton
> >
<div <button
v-for="account in accounts" v-for="account in accounts"
:key="account.id" :key="account.id"
class="_panel _button lcjjdxlm" class="_panel _button lcjjdxlm"
@click="menu(account, $event)" @click="menu(account, $event)"
> >
<div class="avatar"> <div class="avatar">
<MkAvatar :user="account" class="avatar" /> <MkAvatar :user="account" class="avatar" disableLink />
</div> </div>
<div class="body"> <div class="body">
<div class="name"> <div class="name">
@ -23,7 +23,7 @@
<MkAcct :user="account" /> <MkAcct :user="account" />
</div> </div>
</div> </div>
</div> </button>
</FormSuspense> </FormSuspense>
</div> </div>
</template> </template>
@ -158,6 +158,8 @@ definePageMetadata({
.lcjjdxlm { .lcjjdxlm {
display: flex; display: flex;
padding: 16px; padding: 16px;
width: 100%;
text-align: unset;
> .avatar { > .avatar {
display: block; display: block;

View File

@ -204,10 +204,6 @@ hr {
pointer-events: none; pointer-events: none;
} }
&:focus-visible {
outline: none;
}
&:disabled { &:disabled {
opacity: 0.5; opacity: 0.5;
cursor: default; cursor: default;

View File

@ -18,6 +18,7 @@
<MkAvatar <MkAvatar
:user="$i" :user="$i"
class="icon" class="icon"
disableLink
/><!-- <MkAcct class="text" :user="$i"/> --> /><!-- <MkAcct class="text" :user="$i"/> -->
</button> </button>
</div> </div>

View File

@ -18,6 +18,7 @@
<MkAvatar <MkAvatar
:user="$i" :user="$i"
class="icon" class="icon"
disableLink
/><!-- <MkAcct class="text" :user="$i"/> --> /><!-- <MkAcct class="text" :user="$i"/> -->
</button> </button>
</div> </div>
@ -334,6 +335,7 @@ function more(ev: MouseEvent) {
} }
&:hover, &:hover,
&:focus-within,
&.active { &.active {
&:before { &:before {
background: var(--accentLighten); background: var(--accentLighten);
@ -398,8 +400,6 @@ function more(ev: MouseEvent) {
padding-left: 30px; padding-left: 30px;
line-height: 2.85rem; line-height: 2.85rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap; white-space: nowrap;
width: 100%; width: 100%;
text-align: left; text-align: left;
@ -425,9 +425,12 @@ function more(ev: MouseEvent) {
> .text { > .text {
position: relative; position: relative;
font-size: 0.9em; font-size: 0.9em;
overflow: hidden;
text-overflow: ellipsis;
} }
&:hover { &:hover,
&:focus-within {
text-decoration: none; text-decoration: none;
color: var(--navHoverFg); color: var(--navHoverFg);
transition: all 0.4s ease; transition: all 0.4s ease;
@ -438,6 +441,7 @@ function more(ev: MouseEvent) {
} }
&:hover, &:hover,
&:focus-within,
&.active { &.active {
color: var(--accent); color: var(--accent);
transition: all 0.4s ease; transition: all 0.4s ease;
@ -528,6 +532,7 @@ function more(ev: MouseEvent) {
} }
&:hover, &:hover,
&:focus-within,
&.active { &.active {
&:before { &:before {
background: var(--accentLighten); background: var(--accentLighten);
@ -613,6 +618,7 @@ function more(ev: MouseEvent) {
} }
&:hover, &:hover,
&:focus-within,
&.active { &.active {
text-decoration: none; text-decoration: none;
color: var(--accent); color: var(--accent);
@ -642,5 +648,12 @@ function more(ev: MouseEvent) {
} }
} }
} }
.item {
outline: none;
&:focus-visible:before {
outline: auto;
}
}
} }
</style> </style>

View File

@ -83,6 +83,7 @@
<MkAvatar :user="$i" class="avatar" /><MkAcct <MkAvatar :user="$i" class="avatar" /><MkAcct
class="acct" class="acct"
:user="$i" :user="$i"
disableLink
/> />
</button> </button>
<div class="post" @click="post"> <div class="post" @click="post">

View File

@ -5,7 +5,7 @@
class="item _button account" class="item _button account"
@click="openAccountMenu" @click="openAccountMenu"
> >
<MkAvatar :user="$i" class="avatar" /><MkAcct <MkAvatar :user="$i" class="avatar" disableLink /><MkAcct
class="text" class="text"
:user="$i" :user="$i"
/> />
@ -299,6 +299,7 @@ function openInstanceMenu(ev: MouseEvent) {
width: 46px; width: 46px;
height: 46px; height: 46px;
padding: 0; padding: 0;
margin-inline: 0 !important;
} }
} }
@ -372,6 +373,7 @@ function openInstanceMenu(ev: MouseEvent) {
> i { > i {
width: 32px; width: 32px;
justify-content: center;
} }
> i, > i,

View File

@ -227,6 +227,8 @@ onMounted(() => {
} }
.gbhvwtnk { .gbhvwtnk {
display: flex;
justify-content: center;
$ui-font-size: 1em; $ui-font-size: 1em;
$widgets-hide-threshold: 1200px; $widgets-hide-threshold: 1200px;

View File

@ -19,6 +19,12 @@ importers:
'@tensorflow/tfjs': '@tensorflow/tfjs':
specifier: ^3.21.0 specifier: ^3.21.0
version: 3.21.0(seedrandom@3.0.5) version: 3.21.0(seedrandom@3.0.5)
focus-trap:
specifier: ^7.2.0
version: 7.2.0
focus-trap-vue:
specifier: ^4.0.1
version: 4.0.1(focus-trap@7.2.0)(vue@3.2.47)
js-yaml: js-yaml:
specifier: 4.1.0 specifier: 4.1.0
version: 4.1.0 version: 4.1.0
@ -720,8 +726,8 @@ importers:
specifier: 4.19.1 specifier: 4.19.1
version: 4.19.1 version: 4.19.1
browser-image-resizer: browser-image-resizer:
specifier: https://github.com/misskey-dev/browser-image-resizer.git specifier: github:misskey-dev/browser-image-resizer
version: github.com/misskey-dev/browser-image-resizer/0380d12c8e736788ea7f4e6e985175521ea7b23c version: github.com/misskey-dev/browser-image-resizer/56f504427ad7f6500e141a6d9f3aee42023d7f3e
calckey-js: calckey-js:
specifier: workspace:* specifier: workspace:*
version: link:../calckey-js version: link:../calckey-js
@ -1065,6 +1071,11 @@ packages:
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
/@babel/helper-string-parser@7.21.5:
resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-validator-identifier@7.19.1: /@babel/helper-validator-identifier@7.19.1:
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -1108,6 +1119,14 @@ packages:
'@babel/types': 7.21.4 '@babel/types': 7.21.4
dev: true dev: true
/@babel/parser@7.21.5:
resolution: {integrity: sha512-J+IxH2IsxV4HbnTrSWgMAQj0UEo61hDA4Ny8h8PCX0MLXiibqHbqIOVneqdocemSBc22VpBKxt4J6FQzy9HarQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.21.5
dev: false
/@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.4): /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.4):
resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
peerDependencies: peerDependencies:
@ -1294,6 +1313,15 @@ packages:
to-fast-properties: 2.0.0 to-fast-properties: 2.0.0
dev: true dev: true
/@babel/types@7.21.5:
resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.21.5
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
dev: false
/@bcoe/v8-coverage@0.2.3: /@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true dev: true
@ -3805,6 +3833,15 @@ packages:
source-map: 0.6.1 source-map: 0.6.1
dev: true dev: true
/@vue/compiler-core@3.2.47:
resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==}
dependencies:
'@babel/parser': 7.21.5
'@vue/shared': 3.2.47
estree-walker: 2.0.2
source-map: 0.6.1
dev: false
/@vue/compiler-dom@3.2.45: /@vue/compiler-dom@3.2.45:
resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==}
dependencies: dependencies:
@ -3812,6 +3849,13 @@ packages:
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true dev: true
/@vue/compiler-dom@3.2.47:
resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==}
dependencies:
'@vue/compiler-core': 3.2.47
'@vue/shared': 3.2.47
dev: false
/@vue/compiler-sfc@2.7.14: /@vue/compiler-sfc@2.7.14:
resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
dependencies: dependencies:
@ -3835,6 +3879,21 @@ packages:
source-map: 0.6.1 source-map: 0.6.1
dev: true dev: true
/@vue/compiler-sfc@3.2.47:
resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==}
dependencies:
'@babel/parser': 7.21.5
'@vue/compiler-core': 3.2.47
'@vue/compiler-dom': 3.2.47
'@vue/compiler-ssr': 3.2.47
'@vue/reactivity-transform': 3.2.47
'@vue/shared': 3.2.47
estree-walker: 2.0.2
magic-string: 0.25.9
postcss: 8.4.23
source-map: 0.6.1
dev: false
/@vue/compiler-ssr@3.2.45: /@vue/compiler-ssr@3.2.45:
resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==}
dependencies: dependencies:
@ -3842,6 +3901,13 @@ packages:
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true dev: true
/@vue/compiler-ssr@3.2.47:
resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==}
dependencies:
'@vue/compiler-dom': 3.2.47
'@vue/shared': 3.2.47
dev: false
/@vue/reactivity-transform@3.2.45: /@vue/reactivity-transform@3.2.45:
resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==}
dependencies: dependencies:
@ -3852,12 +3918,28 @@ packages:
magic-string: 0.25.9 magic-string: 0.25.9
dev: true dev: true
/@vue/reactivity-transform@3.2.47:
resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==}
dependencies:
'@babel/parser': 7.21.5
'@vue/compiler-core': 3.2.47
'@vue/shared': 3.2.47
estree-walker: 2.0.2
magic-string: 0.25.9
dev: false
/@vue/reactivity@3.2.45: /@vue/reactivity@3.2.45:
resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==}
dependencies: dependencies:
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true dev: true
/@vue/reactivity@3.2.47:
resolution: {integrity: sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==}
dependencies:
'@vue/shared': 3.2.47
dev: false
/@vue/runtime-core@3.2.45: /@vue/runtime-core@3.2.45:
resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==} resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==}
dependencies: dependencies:
@ -3865,6 +3947,13 @@ packages:
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true dev: true
/@vue/runtime-core@3.2.47:
resolution: {integrity: sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==}
dependencies:
'@vue/reactivity': 3.2.47
'@vue/shared': 3.2.47
dev: false
/@vue/runtime-dom@3.2.45: /@vue/runtime-dom@3.2.45:
resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==} resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==}
dependencies: dependencies:
@ -3873,6 +3962,14 @@ packages:
csstype: 2.6.21 csstype: 2.6.21
dev: true dev: true
/@vue/runtime-dom@3.2.47:
resolution: {integrity: sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==}
dependencies:
'@vue/runtime-core': 3.2.47
'@vue/shared': 3.2.47
csstype: 2.6.21
dev: false
/@vue/server-renderer@3.2.45(vue@3.2.45): /@vue/server-renderer@3.2.45(vue@3.2.45):
resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==} resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==}
peerDependencies: peerDependencies:
@ -3883,10 +3980,24 @@ packages:
vue: 3.2.45 vue: 3.2.45
dev: true dev: true
/@vue/server-renderer@3.2.47(vue@3.2.47):
resolution: {integrity: sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==}
peerDependencies:
vue: 3.2.47
dependencies:
'@vue/compiler-ssr': 3.2.47
'@vue/shared': 3.2.47
vue: 3.2.47
dev: false
/@vue/shared@3.2.45: /@vue/shared@3.2.45:
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
dev: true dev: true
/@vue/shared@3.2.47:
resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==}
dev: false
/@webassemblyjs/ast@1.11.1: /@webassemblyjs/ast@1.11.1:
resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==}
dependencies: dependencies:
@ -6074,7 +6185,6 @@ packages:
/csstype@2.6.21: /csstype@2.6.21:
resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
dev: true
/csstype@3.1.1: /csstype@3.1.1:
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
@ -6979,7 +7089,6 @@ packages:
/estree-walker@2.0.2: /estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
/esutils@2.0.3: /esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
@ -7445,6 +7554,22 @@ packages:
readable-stream: 2.3.7 readable-stream: 2.3.7
dev: true dev: true
/focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.2.47):
resolution: {integrity: sha512-2iqOeoSvgq7Um6aL+255a/wXPskj6waLq2oKCa4gOnMORPo15JX7wN6J5bl1SMhMlTlkHXGSrQ9uJPJLPZDl5w==}
peerDependencies:
focus-trap: ^7.0.0
vue: ^3.0.0
dependencies:
focus-trap: 7.2.0
vue: 3.2.47
dev: false
/focus-trap@7.2.0:
resolution: {integrity: sha512-v4wY6HDDYvzkBy4735kW5BUEuw6Yz9ABqMYLuTNbzAFPcBOGiGHwwcNVMvUz4G0kgSYh13wa/7TG3XwTeT4O/A==}
dependencies:
tabbable: 6.1.1
dev: false
/follow-redirects@1.15.2(debug@4.3.4): /follow-redirects@1.15.2(debug@4.3.4):
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
@ -7596,7 +7721,7 @@ packages:
resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==} resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.10
through2: 2.0.5 through2: 2.0.5
dev: true dev: true
@ -9685,7 +9810,7 @@ packages:
/jsonfile@4.0.0: /jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.10
/jsonfile@5.0.0: /jsonfile@5.0.0:
resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==} resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==}
@ -9699,7 +9824,7 @@ packages:
dependencies: dependencies:
universalify: 2.0.0 universalify: 2.0.0
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.10
dev: true dev: true
/jsonld@6.0.0: /jsonld@6.0.0:
@ -10350,7 +10475,6 @@ packages:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
dependencies: dependencies:
sourcemap-codec: 1.4.8 sourcemap-codec: 1.4.8
dev: true
/mailcheck@1.1.1: /mailcheck@1.1.1:
resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==} resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==}
@ -10823,6 +10947,12 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: false
/nanomatch@1.2.13: /nanomatch@1.2.13:
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -11944,6 +12074,15 @@ packages:
picocolors: 1.0.0 picocolors: 1.0.0
source-map-js: 1.0.2 source-map-js: 1.0.2
/postcss@8.4.23:
resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
dev: false
/postgres-array@2.0.0: /postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -13267,7 +13406,6 @@ packages:
/sourcemap-codec@1.4.8: /sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
deprecated: Please use @jridgewell/sourcemap-codec instead deprecated: Please use @jridgewell/sourcemap-codec instead
dev: true
/sparkles@1.0.1: /sparkles@1.0.1:
resolution: {integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==} resolution: {integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==}
@ -13686,6 +13824,10 @@ packages:
resolution: {integrity: sha512-g9rPT3V1Q4WjWFZ/t5BdGC1mT/FpYnsLdBl+M5e6MlRkuE1RSR+R43wcY/3mKI59B9KEr+vxdWCuWNMD3oNHKA==} resolution: {integrity: sha512-g9rPT3V1Q4WjWFZ/t5BdGC1mT/FpYnsLdBl+M5e6MlRkuE1RSR+R43wcY/3mKI59B9KEr+vxdWCuWNMD3oNHKA==}
dev: true dev: true
/tabbable@6.1.1:
resolution: {integrity: sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==}
dev: false
/tapable@2.2.1: /tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -14663,7 +14805,7 @@ packages:
dependencies: dependencies:
append-buffer: 1.0.2 append-buffer: 1.0.2
convert-source-map: 1.9.0 convert-source-map: 1.9.0
graceful-fs: 4.2.11 graceful-fs: 4.2.10
normalize-path: 2.1.1 normalize-path: 2.1.1
now-and-later: 2.0.1 now-and-later: 2.0.1
remove-bom-buffer: 3.0.0 remove-bom-buffer: 3.0.0
@ -14778,6 +14920,16 @@ packages:
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true dev: true
/vue@3.2.47:
resolution: {integrity: sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==}
dependencies:
'@vue/compiler-dom': 3.2.47
'@vue/compiler-sfc': 3.2.47
'@vue/runtime-dom': 3.2.47
'@vue/server-renderer': 3.2.47(vue@3.2.47)
'@vue/shared': 3.2.47
dev: false
/vuedraggable@4.1.0(vue@3.2.45): /vuedraggable@4.1.0(vue@3.2.45):
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
peerDependencies: peerDependencies:
@ -15391,10 +15543,10 @@ packages:
resolution: {integrity: sha512-+MLeeUcLTlnzVo5xDn9+LVN9oX4esvgZ7qfZczBN+YVUvZBafIrPPVyG2WdjMWU2Qkb2ZAh2M8lpqf1wIoGqJQ==} resolution: {integrity: sha512-+MLeeUcLTlnzVo5xDn9+LVN9oX4esvgZ7qfZczBN+YVUvZBafIrPPVyG2WdjMWU2Qkb2ZAh2M8lpqf1wIoGqJQ==}
dev: false dev: false
github.com/misskey-dev/browser-image-resizer/0380d12c8e736788ea7f4e6e985175521ea7b23c: github.com/misskey-dev/browser-image-resizer/56f504427ad7f6500e141a6d9f3aee42023d7f3e:
resolution: {tarball: https://codeload.github.com/misskey-dev/browser-image-resizer/tar.gz/0380d12c8e736788ea7f4e6e985175521ea7b23c} resolution: {tarball: https://codeload.github.com/misskey-dev/browser-image-resizer/tar.gz/56f504427ad7f6500e141a6d9f3aee42023d7f3e}
name: browser-image-resizer name: browser-image-resizer
version: 2.2.1-misskey.3 version: 2.2.1-misskey.4
dev: true dev: true
github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658: github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658: