Unfucked the frontend
ci/woodpecker/push/ociImagePush Pipeline was successful Details

This commit is contained in:
Natty 2024-04-08 03:10:23 +02:00
parent 156ad5c499
commit aefef079a7
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
397 changed files with 6197 additions and 6604 deletions

View File

@ -1,15 +1,15 @@
{ {
"tabWidth": 4, "tabWidth": 4,
"useTabs": false, "useTabs": false,
"singleQuote": false, "singleQuote": false,
"vueIndentScriptAndStyle": false, "vueIndentScriptAndStyle": false,
"plugins": ["vue"], "plugins": ["vue"],
"overrides": [ "overrides": [
{ {
"files": "*.vue", "files": "*.vue",
"options": { "options": {
"parser": "vue" "parser": "vue"
} }
} }
] ]
} }

View File

@ -1,7 +1,7 @@
declare module "@/themes/*.json5" { declare module "@/themes/*.json5" {
import { Theme } from "@/scripts/theme"; import { Theme } from "@/scripts/theme";
const theme: Theme; const theme: Theme;
export default theme; export default theme;
} }

View File

@ -1,7 +1,7 @@
/// <reference types="vue/macros-global" /> /// <reference types="vue/macros-global" />
declare module "*.vue" { declare module "*.vue" {
import type { DefineComponent } from "vue"; import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>; const component: DefineComponent<{}, {}, any>;
export default component; export default component;
} }

View File

@ -1,6 +1,7 @@
{ {
"name": "client", "name": "client",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"watch": "pnpm vite build --watch --mode development", "watch": "pnpm vite build --watch --mode development",
"watchRebuild": "pnpm vite build --watch", "watchRebuild": "pnpm vite build --watch",
@ -9,10 +10,11 @@
}, },
"devDependencies": { "devDependencies": {
"@discordapp/twemoji": "14.1.2", "@discordapp/twemoji": "14.1.2",
"@phosphor-icons/web": "^2.0.3", "@misskey-dev/browser-image-resizer": "^2024.1.0",
"@rollup/plugin-alias": "3.1.9", "@phosphor-icons/web": "^2.1.1",
"@rollup/plugin-json": "4.1.0", "@rollup/plugin-alias": "^5.1.0",
"@rollup/pluginutils": "^4.2.1", "@rollup/plugin-json": "^6.1.0",
"@rollup/pluginutils": "^5.1.0",
"@types/escape-regexp": "0.0.1", "@types/escape-regexp": "0.0.1",
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
"@types/gulp": "4.0.11", "@types/gulp": "4.0.11",
@ -24,14 +26,12 @@
"@types/throttle-debounce": "5.0.0", "@types/throttle-debounce": "5.0.0",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/uuid": "8.3.4", "@types/uuid": "8.3.4",
"@vitejs/plugin-vue": "4.2.3", "@vitejs/plugin-vue": "^5.0.4",
"@vue/compiler-sfc": "3.3.4", "@vue/runtime-core": "^3.4",
"@vue/runtime-core": "3.3.4",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"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": "github:misskey-dev/browser-image-resizer",
"calckey-js": "workspace:*", "calckey-js": "workspace:*",
"chart.js": "4.3.0", "chart.js": "4.3.0",
"chartjs-adapter-date-fns": "3.0.0", "chartjs-adapter-date-fns": "3.0.0",
@ -44,12 +44,12 @@
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "10.11.0", "cypress": "10.11.0",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"emojilib": "github:thatonecalculator/emojilib", "emojilib": "^3.0.12",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eventemitter3": "4.0.7", "eventemitter3": "4.0.7",
"focus-trap": "^7.4.3", "focus-trap": "^7.5.4",
"focus-trap-vue": "^4.0.2", "focus-trap-vue": "^4.0.3",
"gsap": "^3.11.5", "gsap": "^3.12.5",
"idb-keyval": "6.2.1", "idb-keyval": "6.2.1",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"json5": "2.2.3", "json5": "2.2.3",
@ -62,9 +62,8 @@
"prettier-plugin-vue": "1.1.6", "prettier-plugin-vue": "1.1.6",
"prismjs": "1.29.0", "prismjs": "1.29.0",
"punycode": "2.1.1", "punycode": "2.1.1",
"querystring": "0.2.1",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"rollup": "3.23.1", "rollup": "^4.14.1",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass": "1.62.1", "sass": "1.62.1",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
@ -80,17 +79,17 @@
"tsc-alias": "1.8.6", "tsc-alias": "1.8.6",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0", "twemoji-parser": "14.0.0",
"typescript": "5.1.3", "typescript": "^5.4.4",
"unicode-emoji-json": "^0.4.0", "unicode-emoji-json": "^0.4.0",
"uuid": "9.0.0", "uuid": "9.0.0",
"vanilla-tilt": "1.8.0", "vanilla-tilt": "1.8.0",
"vite": "4.3.9", "vidstack": "^1.11",
"vite": "^5.2.8",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vue": "3.3.4", "vue": "^3.4.21",
"vue-component-type-helpers": "1.8.27", "vue-component-type-helpers": "^2.0.11",
"vue-isyourpasswordsafe": "^2.0.0", "vue-isyourpasswordsafe": "^2.0.0",
"vue-plyr": "^7.0.0", "vue3-otp-input": "^0.4.4",
"vue3-otp-input": "^0.4.1",
"vuedraggable": "4.1.0" "vuedraggable": "4.1.0"
} }
} }

View File

@ -49,8 +49,8 @@ export async function signout() {
.then((registrations) => { .then((registrations) => {
return Promise.all( return Promise.all(
registrations.map((registration) => registrations.map((registration) =>
registration.unregister() registration.unregister(),
) ),
); );
}); });
} }
@ -80,7 +80,7 @@ export async function removeAccount(id: Account["id"]) {
const accounts = await getAccounts(); const accounts = await getAccounts();
accounts.splice( accounts.splice(
accounts.findIndex((x) => x.id === id), accounts.findIndex((x) => x.id === id),
1 1,
); );
if (accounts.length > 0) await set("accounts", accounts); if (accounts.length > 0) await set("accounts", accounts);
@ -158,12 +158,12 @@ export async function openAccountMenu(
active?: misskey.entities.UserDetailed["id"]; active?: misskey.entities.UserDetailed["id"];
onChoose?: (account: misskey.entities.UserDetailed) => void; onChoose?: (account: misskey.entities.UserDetailed) => void;
}, },
ev: MouseEvent ev: MouseEvent,
) { ) {
function showSigninDialog() { function showSigninDialog() {
popup( popup(
defineAsyncComponent( defineAsyncComponent(
() => import("@/components/MkSigninDialog.vue") () => import("@/components/MkSigninDialog.vue"),
), ),
{}, {},
{ {
@ -172,14 +172,14 @@ export async function openAccountMenu(
success(); success();
}, },
}, },
"closed" "closed",
); );
} }
function createAccount() { function createAccount() {
popup( popup(
defineAsyncComponent( defineAsyncComponent(
() => import("@/components/MkSignupDialog.vue") () => import("@/components/MkSignupDialog.vue"),
), ),
{}, {},
{ {
@ -188,7 +188,7 @@ export async function openAccountMenu(
switchAccountWithToken(res.i); switchAccountWithToken(res.i);
}, },
}, },
"closed" "closed",
); );
} }
@ -203,7 +203,7 @@ export async function openAccountMenu(
} }
const storedAccounts = await getAccounts().then((accounts) => const storedAccounts = await getAccounts().then((accounts) =>
accounts.filter((x) => x.id !== $i.id) accounts.filter((x) => x.id !== $i.id),
); );
const accountsPromise = api("users/show", { const accountsPromise = api("users/show", {
userIds: storedAccounts.map((x) => x.id), userIds: storedAccounts.map((x) => x.id),
@ -232,7 +232,7 @@ export async function openAccountMenu(
if (account == null) return res(null); if (account == null) return res(null);
res(createItem(account)); res(createItem(account));
}); });
}) }),
); );
if (opts.withExtraOperation) { if (opts.withExtraOperation) {
@ -286,7 +286,7 @@ export async function openAccountMenu(
ev.currentTarget ?? ev.target, ev.currentTarget ?? ev.target,
{ {
align: "left", align: "left",
} },
); );
} else { } else {
popupMenu( popupMenu(
@ -297,7 +297,7 @@ export async function openAccountMenu(
ev.currentTarget ?? ev.target, ev.currentTarget ?? ev.target,
{ {
align: "left", align: "left",
} },
); );
} }
} }

View File

@ -26,11 +26,11 @@ watch(
{ {
id: userIds.map((u) => (typeof u === "string" ? u : u.id)), id: userIds.map((u) => (typeof u === "string" ? u : u.id)),
}, },
{} {},
) )
.then((p) => p.filter((u) => u !== null).map((u) => u!)); .then((p) => p.filter((u) => u !== null).map((u) => u!));
}, },
{ immediate: true, deep: true } { immediate: true, deep: true },
); );
</script> </script>

View File

@ -43,7 +43,7 @@ const props = withDefaults(
direction: "down", direction: "down",
reversed: false, reversed: false,
noGap: false, noGap: false,
} },
); );
type ItemType = type ItemType =
@ -82,12 +82,12 @@ watch(
effectiveItems.value = listItems; effectiveItems.value = listItems;
}, },
{ deep: true, immediate: true } { deep: true, immediate: true },
); );
function getDateText(item: { createdAt: string } | { created_at: string }) { function getDateText(item: { createdAt: string } | { created_at: string }) {
const dateTime = new Date( const dateTime = new Date(
magTransProperty(item, "createdAt", "created_at") magTransProperty(item, "createdAt", "created_at"),
); );
const date = dateTime.getDate(); const date = dateTime.getDate();
const month = dateTime.getMonth() + 1; const month = dateTime.getMonth() + 1;
@ -100,11 +100,11 @@ function getDateText(item: { createdAt: string } | { created_at: string }) {
const animationEnabled = ref(defaultStore.state.animation); const animationEnabled = ref(defaultStore.state.animation);
watch( watch(
() => defaultStore.state.animation, () => defaultStore.state.animation,
(val) => (animationEnabled.value = val) (val) => (animationEnabled.value = val),
); );
const parentType = computed(() => const parentType = computed(() =>
animationEnabled ? TransitionGroup : HTMLDivElement animationEnabled ? TransitionGroup : HTMLDivElement,
); );
const tag = computed(() => (animationEnabled ? "div" : null)); const tag = computed(() => (animationEnabled ? "div" : null));
const name = computed(() => (animationEnabled ? "list" : null)); const name = computed(() => (animationEnabled ? "list" : null));
@ -129,7 +129,8 @@ defineSlots<{
} }
> .list-enter-active { > .list-enter-active {
transition: transform 0.7s cubic-bezier(0.23, 1, 0.32, 1), transition:
transform 0.7s cubic-bezier(0.23, 1, 0.32, 1),
opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1); opacity 0.7s cubic-bezier(0.23, 1, 0.32, 1);
} }

View File

@ -19,7 +19,7 @@ const props = defineProps<{
}>(); }>();
const url = computed( const url = computed(
() => `https://matrix.to/#/@${props.username}:${props.host}` () => `https://matrix.to/#/@${props.username}:${props.host}`,
); );
</script> </script>

View File

@ -263,7 +263,7 @@ const props = defineProps<{
collapsedReply?: boolean; collapsedReply?: boolean;
}>(); }>();
let note = $ref<packed.PackNoteMaybeFull>(structuredClone(toRaw(props.note))); let note = ref<packed.PackNoteMaybeFull>(structuredClone(toRaw(props.note)));
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason; if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -282,13 +282,15 @@ const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const renoteTime = ref<HTMLElement | null>(null); const renoteTime = ref<HTMLElement | null>(null);
const reactButton = ref<HTMLElement | null>(null); const reactButton = ref<HTMLElement | null>(null);
let appearNote = $computed( let appearNote = computed(
() => magEffectiveNote(note) as packed.PackNoteMaybeFull () => magEffectiveNote(note.value) as packed.PackNoteMaybeFull
); );
const isMyRenote = $i && $i.id === appearNote.user.id; const isMyRenote = computed(() => $i && $i.id === appearNote.value.user.id);
const showContent = ref(false); const showContent = ref(false);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref(getWordSoftMute(note, $i, defaultStore.state.mutedWords)); const muted = computed(() =>
getWordSoftMute(note.value, $i, defaultStore.state.mutedWords)
);
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
const enableEmojiReactions = defaultStore.state.enableEmojiReactions; const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
@ -307,7 +309,7 @@ const keymap = {
useNoteCapture({ useNoteCapture({
rootEl: el, rootEl: el,
note: $$(appearNote), note: appearNote,
isDeletedRef: isDeleted, isDeletedRef: isDeleted,
}); });
@ -331,7 +333,7 @@ function react(viaKeyboard = false): void {
reactButton.value, reactButton.value,
(reaction) => { (reaction) => {
os.api("notes/reactions/create", { os.api("notes/reactions/create", {
noteId: appearNote.id, noteId: appearNote.value.id,
reaction: magReactionToLegacy(reaction), reaction: magReactionToLegacy(reaction),
}); });
}, },
@ -374,21 +376,24 @@ function onContextmenu(ev: MouseEvent): void {
[ [
{ {
type: "label", type: "label",
text: notePage(appearNote), text: notePage(appearNote.value),
}, },
{ {
icon: "ph-browser ph-bold ph-lg", icon: "ph-browser ph-bold ph-lg",
text: i18n.ts.openInWindow, text: i18n.ts.openInWindow,
action: () => { action: () => {
os.pageWindow(notePage(appearNote)); os.pageWindow(notePage(appearNote.value));
}, },
}, },
notePage(appearNote) != location.pathname notePage(appearNote.value) != location.pathname
? { ? {
icon: "ph-arrows-out-simple ph-bold ph-lg", icon: "ph-arrows-out-simple ph-bold ph-lg",
text: i18n.ts.showInPage, text: i18n.ts.showInPage,
action: () => { action: () => {
router.push(notePage(appearNote), "forcePage"); router.push(
notePage(appearNote.value),
"forcePage"
);
}, },
} }
: undefined, : undefined,
@ -397,22 +402,25 @@ function onContextmenu(ev: MouseEvent): void {
type: "a", type: "a",
icon: "ph-arrow-square-out ph-bold ph-lg", icon: "ph-arrow-square-out ph-bold ph-lg",
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
href: notePage(appearNote), href: notePage(appearNote.value),
target: "_blank", target: "_blank",
}, },
{ {
icon: "ph-link-simple ph-bold ph-lg", icon: "ph-link-simple ph-bold ph-lg",
text: i18n.ts.copyLink, text: i18n.ts.copyLink,
action: () => { action: () => {
copyToClipboard(`${url}${notePage(appearNote)}`); copyToClipboard(`${url}${notePage(appearNote.value)}`);
}, },
}, },
appearNote.user.host != null appearNote.value.user.host != null
? { ? {
type: "a", type: "a",
icon: "ph-arrow-square-up-right ph-bold ph-lg", icon: "ph-arrow-square-up-right ph-bold ph-lg",
text: i18n.ts.showOnRemote, text: i18n.ts.showOnRemote,
href: appearNote.url ?? appearNote.uri ?? "", href:
appearNote.value.url ??
appearNote.value.uri ??
"",
target: "_blank", target: "_blank",
} }
: undefined, : undefined,
@ -425,7 +433,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void { function menu(viaKeyboard = false): void {
os.popupMenu( os.popupMenu(
getNoteMenu({ getNoteMenu({
note: note, note: note.value,
translating, translating,
translation, translation,
menuButton: menuButton.value, menuButton: menuButton.value,
@ -449,7 +457,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true, danger: true,
action: () => { action: () => {
os.api("notes/delete", { os.api("notes/delete", {
noteId: note.id, noteId: note.value.id,
}); });
isDeleted.value = true; isDeleted.value = true;
}, },
@ -490,7 +498,7 @@ function noteClick(e) {
) { ) {
e.stopPropagation(); e.stopPropagation();
} else { } else {
router.push(notePage(appearNote)); router.push(notePage(appearNote.value));
} }
} }
@ -501,30 +509,30 @@ function setPostExpanded(val: boolean) {
} }
const accessibleLabel = computed(() => { const accessibleLabel = computed(() => {
let label = `${appearNote.user.username}; `; let label = `${appearNote.value.user.username}; `;
if (appearNote.renoted_note) { if (appearNote.value.renoted_note) {
label += `${i18n.t("renoted")} ${ label += `${i18n.t("renoted")} ${
appearNote.renoted_note.user.username appearNote.value.renoted_note.user.username
}; `; }; `;
if (appearNote.renoted_note.cw) { if (appearNote.value.renoted_note.cw) {
label += `${i18n.t("cw")}: ${appearNote.renoted_note.cw}; `; label += `${i18n.t("cw")}: ${appearNote.value.renoted_note.cw}; `;
if (postIsExpanded.value) { if (postIsExpanded.value) {
label += `${appearNote.renoted_note.text}; `; label += `${appearNote.value.renoted_note.text}; `;
} }
} else { } else {
label += `${appearNote.renoted_note.text}; `; label += `${appearNote.value.renoted_note.text}; `;
} }
} else { } else {
if (appearNote.cw) { if (appearNote.value.cw) {
label += `${i18n.t("cw")}: ${appearNote.cw}; `; label += `${i18n.t("cw")}: ${appearNote.value.cw}; `;
if (postIsExpanded.value) { if (postIsExpanded.value) {
label += `${appearNote.text}; `; label += `${appearNote.value.text}; `;
} }
} else { } else {
label += `${appearNote.text}; `; label += `${appearNote.value.text}; `;
} }
} }
const date = new Date(appearNote.created_at); const date = new Date(appearNote.value.created_at);
label += `${date.toLocaleTimeString()}`; label += `${date.toLocaleTimeString()}`;
return label; return label;
}); });

View File

@ -152,7 +152,16 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, onUpdated, Ref, ref, toRaw, watch } from "vue"; import {
computed,
onMounted,
onUnmounted,
onUpdated,
Ref,
ref,
toRaw,
watch,
} from "vue";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import MkTab from "@/components/MkTab.vue"; import MkTab from "@/components/MkTab.vue";
import MagNoteSub from "@/components/MagNoteSub.vue"; import MagNoteSub from "@/components/MagNoteSub.vue";
@ -185,9 +194,9 @@ const props = defineProps<{
pinned?: boolean; pinned?: boolean;
}>(); }>();
let tab = $ref("replies"); let tab = ref("replies");
let note = $ref<packed.PackNoteMaybeFull>(structuredClone(toRaw(props.note))); let note = ref<packed.PackNoteMaybeFull>(structuredClone(toRaw(props.note)));
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
if (what === "note") return i18n.ts.userSaysSomethingReason; if (what === "note") return i18n.ts.userSaysSomethingReason;
@ -204,14 +213,14 @@ const noteEl = ref<HTMLElement | null>(null);
const menuButton = ref<HTMLElement | null>(null); const menuButton = ref<HTMLElement | null>(null);
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement | null>(null); const reactButton = ref<HTMLElement | null>(null);
let appearNote = $computed( let appearNote = computed(
() => magEffectiveNote(note) as packed.PackNoteMaybeFull () => magEffectiveNote(note.value) as packed.PackNoteMaybeFull,
); );
let parentNote = $ref(appearNote.parent_note); let parentNote = ref(appearNote.value.parent_note);
const showContent = ref(false); const showContent = ref(false);
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref( const muted = ref(
getWordSoftMute(appearNote, $i, defaultStore.state.mutedWords) getWordSoftMute(appearNote.value, $i, defaultStore.state.mutedWords),
); );
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
@ -234,7 +243,7 @@ const keymap = {
useNoteCapture({ useNoteCapture({
rootEl: el as Ref<HTMLElement>, rootEl: el as Ref<HTMLElement>,
note: $$(appearNote), note: appearNote,
isDeletedRef: isDeleted, isDeletedRef: isDeleted,
}); });
@ -255,13 +264,13 @@ function react(viaKeyboard = false): void {
reactButton.value, reactButton.value,
(reaction) => { (reaction) => {
os.api("notes/reactions/create", { os.api("notes/reactions/create", {
noteId: appearNote.id, noteId: appearNote.value.id,
reaction: magReactionToLegacy(reaction), reaction: magReactionToLegacy(reaction),
}); });
}, },
() => { () => {
focus(); focus();
} },
); );
} }
@ -281,13 +290,13 @@ function onContextmenu(ev: MouseEvent): void {
} else { } else {
os.contextMenu( os.contextMenu(
getNoteMenu({ getNoteMenu({
note: note, note: note.value,
translating, translating,
translation, translation,
menuButton: menuButton.value, menuButton: menuButton.value,
isDeleted, isDeleted,
}), }),
ev ev,
).then(focus); ).then(focus);
} }
} }
@ -295,7 +304,7 @@ function onContextmenu(ev: MouseEvent): void {
function menu(viaKeyboard = false): void { function menu(viaKeyboard = false): void {
os.popupMenu( os.popupMenu(
getNoteMenu({ getNoteMenu({
note: note, note: note.value,
translating, translating,
translation, translation,
menuButton: menuButton.value, menuButton: menuButton.value,
@ -304,7 +313,7 @@ function menu(viaKeyboard = false): void {
menuButton.value ?? undefined, menuButton.value ?? undefined,
{ {
viaKeyboard, viaKeyboard,
} },
).then(focus); ).then(focus);
} }
@ -318,64 +327,63 @@ function blur() {
async function updateParent(note: packed.PackNoteMaybeFull) { async function updateParent(note: packed.PackNoteMaybeFull) {
if (note.parent_note) { if (note.parent_note) {
parentNote = note.parent_note; parentNote.value = note.parent_note;
return; return;
} else if (note.parent_note_id) { } else if (note.parent_note_id) {
parentNote = null; parentNote.value = null;
parentNote = await os.magApi( parentNote.value = await os.magApi(
endpoints.GetNoteById, endpoints.GetNoteById,
{ attachments: true, context: true }, { attachments: true, context: true },
{ id: note.parent_note_id } { id: note.parent_note_id },
); );
return; return;
} }
parentNote = null; parentNote.value = null;
} }
watch(appearNote, updateParent); watch(appearNote, updateParent, { immediate: true });
updateParent(note);
os.api("notes/children", { os.api("notes/children", {
noteId: appearNote.id, noteId: appearNote.value.id,
limit: 30, limit: 30,
depth: 12, depth: 12,
}).then((res) => { }).then((res) => {
res = res.reduce( res = res.reduce(
(acc: misskey.entities.Note[], resNote: misskey.entities.Note) => { (acc: misskey.entities.Note[], resNote: misskey.entities.Note) => {
if (resNote.userId == appearNote.user.id) { if (resNote.userId == appearNote.value.user.id) {
return [...acc, resNote]; return [...acc, resNote];
} }
return [resNote, ...acc]; return [resNote, ...acc];
}, },
[] [],
); );
Promise.all(res.map(resolveNote)).then((a) => { Promise.all(res.map(resolveNote)).then((a) => {
replies.value = a; replies.value = a;
directReplies.value = a directReplies.value = a
.filter((resNote) => resNote.parent_note_id === appearNote.id) .filter((resNote) => resNote.parent_note_id === appearNote.value.id)
.reverse(); .reverse();
directQuotes.value = a.filter( directQuotes.value = a.filter(
(resNote) => resNote.renoted_note_id === appearNote.id (resNote) => resNote.renoted_note_id === appearNote.value.id,
); );
}); });
}); });
if (appearNote.parent_note_id) { if (appearNote.value.parent_note_id) {
os.api("notes/conversation", { os.api("notes/conversation", {
noteId: appearNote.parent_note_id, noteId: appearNote.value.parent_note_id,
limit: 30, limit: 30,
}).then(async (res) => { }).then(async (res) => {
conversation.value = await Promise.all<packed.PackNoteMaybeFull>( conversation.value = await Promise.all<packed.PackNoteMaybeFull>(
res?.reverse().map(resolveNote) res?.reverse().map(resolveNote),
); );
focus(); focus();
}); });
} }
os.api("notes/clips", { os.api("notes/clips", {
noteId: appearNote.id, noteId: appearNote.value.id,
}).then((res) => { }).then((res) => {
clips.value = res as misskey.entities.Clip[]; clips.value = res as misskey.entities.Clip[];
}); });
@ -389,9 +397,9 @@ os.api("notes/clips", {
// const pagingComponent = $ref<InstanceType<typeof MkPagination>>(); // const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
function loadTab() { function loadTab() {
if (tab === "renotes" && !renotes.value) { if (tab.value === "renotes" && !renotes.value) {
os.api("notes/renotes", { os.api("notes/renotes", {
noteId: appearNote.id, noteId: appearNote.value.id,
limit: 100, limit: 100,
}).then((res) => { }).then((res) => {
renotes.value = res; renotes.value = res;
@ -403,7 +411,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
const { type, id, body } = noteData; const { type, id, body } = noteData;
let found = -1; let found = -1;
if (id === appearNote.id) { if (id === appearNote.value.id) {
found = 0; found = 0;
} else { } else {
for (let i = 0; i < replies.value.length; i++) { for (let i = 0; i < replies.value.length; i++) {
@ -430,7 +438,7 @@ async function onNoteUpdated(noteData: NoteUpdatedEvent): Promise<void> {
}, },
{ {
id: createdId, id: createdId,
} },
); );
replies.value.splice(found, 0, replyNote); replies.value.splice(found, 0, replyNote);
@ -566,7 +574,9 @@ onUnmounted(() => {
background: var(--panelHighlight); background: var(--panelHighlight);
border-radius: var(--radius); border-radius: var(--radius);
opacity: 0; opacity: 0;
transition: opacity 0.2s, background 0.2s; transition:
opacity 0.2s,
background 0.2s;
z-index: -1; z-index: -1;
} }
&.reply-to { &.reply-to {

View File

@ -23,10 +23,10 @@
v-tooltip.noDelay=" v-tooltip.noDelay="
i18n.t('edited', { i18n.t('edited', {
date: new Date( date: new Date(
note.updated_at note.updated_at,
).toLocaleDateString(), ).toLocaleDateString(),
time: new Date( time: new Date(
note.updated_at note.updated_at,
).toLocaleTimeString(), ).toLocaleTimeString(),
}) })
" "

View File

@ -39,6 +39,6 @@ watch(
noteData.value = n; noteData.value = n;
}); });
}, },
{ immediate: true } { immediate: true },
); );
</script> </script>

View File

@ -15,7 +15,7 @@ import XNoteHeader from "@/components/MagNoteHeader.vue";
import MagSubNoteContent from "@/components/MagSubNoteContent.vue"; import MagSubNoteContent from "@/components/MagSubNoteContent.vue";
import { packed } from "magnetar-common"; import { packed } from "magnetar-common";
const props = defineProps<{ defineProps<{
note: packed.PackNoteMaybeFull; note: packed.PackNoteMaybeFull;
pinned?: boolean; pinned?: boolean;
}>(); }>();

View File

@ -4,7 +4,7 @@
ref="el" ref="el"
v-size="{ max: [450, 500] }" v-size="{ max: [450, 500] }"
class="wrpstxzv" class="wrpstxzv"
:id="detailedView ? appearNote.id : null" :id="detailedView ? appearNote.id : undefined"
tabindex="-1" tabindex="-1"
:class="{ :class="{
children: depth > 1, children: depth > 1,
@ -170,7 +170,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Ref } from "vue"; import type { Ref } from "vue";
import { inject, ref, toRaw, watch } from "vue"; import { computed, inject, ref, toRaw } from "vue";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import XNoteHeader from "@/components/MagNoteHeader.vue"; import XNoteHeader from "@/components/MagNoteHeader.vue";
import XSubNoteContent from "@/components/MagSubNoteContent.vue"; import XSubNoteContent from "@/components/MagSubNoteContent.vue";
@ -219,18 +219,11 @@ const props = withDefaults(
{ {
depth: 1, depth: 1,
replyLevel: 1, replyLevel: 1,
} },
); );
let noteCpy = $ref<packed.PackNoteMaybeFull>( let noteCpy = computed<packed.PackNoteMaybeFull>(() =>
structuredClone(toRaw(props.note)) structuredClone(toRaw(props.note)),
);
watch(
() => props.note,
(val) => {
noteCpy = structuredClone(toRaw(val));
}
); );
const softMuteReasonI18nSrc = (what?: string) => { const softMuteReasonI18nSrc = (what?: string) => {
@ -249,19 +242,21 @@ const menuButton = ref<HTMLElement | null>(null);
const starButton = ref<InstanceType<typeof XStarButton>>(); const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();
const reactButton = ref<HTMLElement | null>(null); const reactButton = ref<HTMLElement | null>(null);
let appearNote = $computed( let appearNote = computed(
() => magEffectiveNote(noteCpy) as packed.PackNoteMaybeFull () => magEffectiveNote(noteCpy.value) as packed.PackNoteMaybeFull,
); );
const isDeleted = ref(false); const isDeleted = ref(false);
const muted = ref(getWordSoftMute(noteCpy, $i, defaultStore.state.mutedWords)); const muted = computed(() =>
getWordSoftMute(noteCpy.value, $i, defaultStore.state.mutedWords),
);
const translation = ref(null); const translation = ref(null);
const translating = ref(false); const translating = ref(false);
const replies: packed.PackNoteMaybeFull[] = const replies: packed.PackNoteMaybeFull[] =
props.conversation props.conversation
?.filter( ?.filter(
(item) => (item) =>
item.parent_note_id === appearNote.id || item.parent_note_id === appearNote.value.id ||
item.renoted_note_id === appearNote.id item.renoted_note_id === appearNote.value.id,
) )
.reverse() ?? []; .reverse() ?? [];
const enableEmojiReactions = defaultStore.state.enableEmojiReactions; const enableEmojiReactions = defaultStore.state.enableEmojiReactions;
@ -269,7 +264,7 @@ const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
useNoteCapture({ useNoteCapture({
rootEl: el as Ref<HTMLElement>, rootEl: el as Ref<HTMLElement>,
note: $$(appearNote), note: appearNote,
isDeletedRef: isDeleted, isDeletedRef: isDeleted,
}); });
@ -290,13 +285,13 @@ function react(viaKeyboard = false): void {
reactButton.value, reactButton.value,
(reaction) => { (reaction) => {
os.api("notes/reactions/create", { os.api("notes/reactions/create", {
noteId: appearNote.id, noteId: appearNote.value.id,
reaction: magReactionToLegacy(reaction), reaction: magReactionToLegacy(reaction),
}); });
}, },
() => { () => {
focus(); focus();
} },
); );
} }
@ -310,13 +305,13 @@ function undoReact(note: packed.PackNoteBase): void {
const currentClipPage = inject<Ref<misskey.entities.Clip> | null>( const currentClipPage = inject<Ref<misskey.entities.Clip> | null>(
"currentClipPage", "currentClipPage",
null null,
); );
function menu(viaKeyboard = false): void { function menu(viaKeyboard = false): void {
os.popupMenu( os.popupMenu(
getNoteMenu({ getNoteMenu({
note: noteCpy, note: noteCpy.value,
translating, translating,
translation, translation,
menuButton: menuButton.value, menuButton: menuButton.value,
@ -326,7 +321,7 @@ function menu(viaKeyboard = false): void {
menuButton.value ?? undefined, menuButton.value ?? undefined,
{ {
viaKeyboard, viaKeyboard,
} },
).then(focus); ).then(focus);
} }
@ -350,21 +345,24 @@ function onContextmenu(ev: MouseEvent): void {
[ [
{ {
type: "label", type: "label",
text: notePage(appearNote), text: notePage(appearNote.value),
}, },
{ {
icon: "ph-browser ph-bold ph-lg", icon: "ph-browser ph-bold ph-lg",
text: i18n.ts.openInWindow, text: i18n.ts.openInWindow,
action: () => { action: () => {
os.pageWindow(notePage(appearNote)); os.pageWindow(notePage(appearNote.value));
}, },
}, },
notePage(appearNote) != location.pathname notePage(appearNote.value) != location.pathname
? { ? {
icon: "ph-arrows-out-simple ph-bold ph-lg", icon: "ph-arrows-out-simple ph-bold ph-lg",
text: i18n.ts.showInPage, text: i18n.ts.showInPage,
action: () => { action: () => {
router.push(notePage(appearNote), "forcePage"); router.push(
notePage(appearNote.value),
"forcePage",
);
}, },
} }
: undefined, : undefined,
@ -373,27 +371,27 @@ function onContextmenu(ev: MouseEvent): void {
type: "a", type: "a",
icon: "ph-arrow-square-out ph-bold ph-lg", icon: "ph-arrow-square-out ph-bold ph-lg",
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
href: notePage(appearNote), href: notePage(appearNote.value),
target: "_blank", target: "_blank",
}, },
{ {
icon: "ph-link-simple ph-bold ph-lg", icon: "ph-link-simple ph-bold ph-lg",
text: i18n.ts.copyLink, text: i18n.ts.copyLink,
action: () => { action: () => {
copyToClipboard(`${url}${notePage(appearNote)}`); copyToClipboard(`${url}${notePage(appearNote.value)}`);
}, },
}, },
noteCpy.user.host != null noteCpy.value.user.host != null
? { ? {
type: "a", type: "a",
icon: "ph-arrow-square-up-right ph-bold ph-lg", icon: "ph-arrow-square-up-right ph-bold ph-lg",
text: i18n.ts.showOnRemote, text: i18n.ts.showOnRemote,
href: noteCpy.url ?? noteCpy.uri ?? "", href: noteCpy.value.url ?? noteCpy.value.uri ?? "",
target: "_blank", target: "_blank",
} }
: undefined, : undefined,
], ],
ev ev,
); );
} }
} }

View File

@ -274,7 +274,7 @@ const props = withDefaults(
{ {
withTime: false, withTime: false,
full: false, full: false,
} },
); );
const emit = defineEmits(["refresh"]); const emit = defineEmits(["refresh"]);
@ -310,7 +310,7 @@ onMounted(() => {
() => props.notification.is_read, () => props.notification.is_read,
() => { () => {
readObserver!.disconnect(); readObserver!.disconnect();
} },
); );
} }
}); });
@ -333,7 +333,7 @@ useTooltip(reactionRef, (showing) => {
targetElement: reactionRef.value.$el, targetElement: reactionRef.value.$el,
}, },
{}, {},
"closed" "closed",
); );
}); });
</script> </script>

View File

@ -143,7 +143,7 @@ const props = withDefaults(
{ {
withTime: false, withTime: false,
full: false, full: false,
} },
); );
const firstNotification = ref(props.notificationGroup.value[0]); const firstNotification = ref(props.notificationGroup.value[0]);
@ -152,7 +152,7 @@ watch(
(n) => { (n) => {
firstNotification.value = n; firstNotification.value = n;
}, },
{ deep: true } { deep: true },
); );
const emit = defineEmits(["refresh"]); const emit = defineEmits(["refresh"]);
@ -175,7 +175,7 @@ onMounted(() => {
if (!entries.some((entry) => entry.isIntersecting)) return; if (!entries.some((entry) => entry.isIntersecting)) return;
stream.send( stream.send(
"readNotifications", "readNotifications",
props.notificationGroup.value.map((n) => n.id) props.notificationGroup.value.map((n) => n.id),
); );
observer.disconnect(); observer.disconnect();
}); });
@ -189,7 +189,7 @@ onMounted(() => {
() => firstNotification.value.is_read, () => firstNotification.value.is_read,
() => { () => {
readObserver!.disconnect(); readObserver!.disconnect();
} },
); );
} }
}); });
@ -201,7 +201,7 @@ onUnmounted(() => {
const hookTooltip = ( const hookTooltip = (
el: HTMLElement | ComponentPublicInstance | null, el: HTMLElement | ComponentPublicInstance | null,
notif: packed.PackNotification notif: packed.PackNotification,
) => { ) => {
if (el == null) return; if (el == null) return;
@ -219,7 +219,7 @@ const hookTooltip = (
"$el" in elRef.value ? elRef.value.$el : elRef.value, "$el" in elRef.value ? elRef.value.$el : elRef.value,
}, },
{}, {},
"closed" "closed",
); );
}); });

View File

@ -39,7 +39,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {} from "vue"; import { computed, ref } from "vue";
import MkSwitch from "./form/switch.vue"; import MkSwitch from "./form/switch.vue";
import MkInfo from "./MkInfo.vue"; import MkInfo from "./MkInfo.vue";
import MkButton from "./MkButton.vue"; import MkButton from "./MkButton.vue";
@ -60,20 +60,20 @@ const props = withDefaults(
{ {
includingTypes: () => notificationTypes, includingTypes: () => notificationTypes,
showGlobalToggle: true, showGlobalToggle: true,
} },
); );
let includingTypes = $computed(() => props.includingTypes || []); let includingTypes = computed(() => props.includingTypes || []);
const dialog = $ref<InstanceType<typeof XModalWindow>>(); const dialog = ref<InstanceType<typeof XModalWindow>>();
let typesMap = $ref( let typesMap = ref(
Object.fromEntries( Object.fromEntries(
notificationTypes.map((n) => [n, includingTypes.includes(n)]) notificationTypes.map((n) => [n, includingTypes.value.includes(n)]),
) as Record<types.NotificationType, boolean> ) as Record<types.NotificationType, boolean>,
); );
let useGlobalSetting = $ref(includingTypes === null || props.showGlobalToggle); let useGlobalSetting = ref(includingTypes === null || props.showGlobalToggle);
function ok() { function ok() {
if (useGlobalSetting) { if (useGlobalSetting) {
@ -84,7 +84,7 @@ function ok() {
}); });
} }
dialog?.close(); dialog.value?.close();
} }
function disableAll() { function disableAll() {

View File

@ -15,7 +15,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from "vue"; import { onMounted, ref } from "vue";
import XNotification from "@/components/MagNotification.vue"; import XNotification from "@/components/MagNotification.vue";
import * as os from "@/os"; import * as os from "@/os";
@ -28,11 +28,11 @@ const emit = defineEmits<{
}>(); }>();
const zIndex = os.claimZIndex("high"); const zIndex = os.claimZIndex("high");
let showing = $ref(true); let showing = ref(true);
onMounted(() => { onMounted(() => {
window.setTimeout(() => { window.setTimeout(() => {
showing = false; showing.value = false;
}, 6000); }, 6000);
}); });
</script> </script>
@ -40,7 +40,9 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.notification-toast-enter-active, .notification-toast-enter-active,
.notification-toast-leave-active { .notification-toast-leave-active {
transition: opacity 0.3s, transform 0.3s !important; transition:
opacity 0.3s,
transform 0.3s !important;
} }
.notification-toast-enter-from, .notification-toast-enter-from,
.notification-toast-leave-to { .notification-toast-leave-to {

View File

@ -26,7 +26,7 @@
v-slot="{ v-slot="{
item: notificationGroup, item: notificationGroup,
}: { }: {
item: NotificationGroup, item: NotificationGroup;
}" }"
class="elsfgstc" class="elsfgstc"
:items="items" :items="items"
@ -140,7 +140,7 @@ const allowedGroups: types.NotificationType[] = ["Reaction", "Renote"];
watch( watch(
() => props.includeTypes, () => props.includeTypes,
() => reload() () => reload(),
); );
const onNotification = (notification: packed.PackNotification) => { const onNotification = (notification: packed.PackNotification) => {
@ -225,7 +225,7 @@ const emit = defineEmits<{
// 5. [MAYBE] Items that would be pushed out by another item CAN break boundaries, but only with adjacent items // 5. [MAYBE] Items that would be pushed out by another item CAN break boundaries, but only with adjacent items
const groupIntra = ( const groupIntra = (
notifications: packed.PackNotification[] notifications: packed.PackNotification[],
): NotificationGroup[] => { ): NotificationGroup[] => {
const x = notifications const x = notifications
.map( .map(
@ -235,13 +235,13 @@ const groupIntra = (
id: n.id, id: n.id,
created_at: n.created_at, created_at: n.created_at,
value: n, value: n,
} as NotificationGroup) }) as NotificationGroup,
) )
.map((g) => [g]) .map((g) => [g])
.reduce( .reduce(
( (
prev: NotificationGroup[], prev: NotificationGroup[],
[curr]: NotificationGroup[] [curr]: NotificationGroup[],
): NotificationGroup[] => { ): NotificationGroup[] => {
const currGroup = curr as NotificationGroup & { const currGroup = curr as NotificationGroup & {
type: "single"; type: "single";
@ -295,7 +295,7 @@ const groupIntra = (
magEffectiveNote(n.note).id === magEffectiveNote(n.note).id ===
magEffectiveNote(currNoteGroup.value.note) magEffectiveNote(currNoteGroup.value.note)
.id && .id &&
n.type === currNoteGroup.value.type n.type === currNoteGroup.value.type,
) )
) { ) {
p.value = [...p.value, currNoteGroup.value]; p.value = [...p.value, currNoteGroup.value];
@ -317,14 +317,14 @@ const groupIntra = (
return [...prev, currGroup]; return [...prev, currGroup];
}, },
[] as NotificationGroup[] [] as NotificationGroup[],
); );
return x; return x;
}; };
const fetch = async ( const fetch = async (
pagination: SpanFilter pagination: SpanFilter,
): Promise<PaginatedResult<packed.PackNotification[]>> => { ): Promise<PaginatedResult<packed.PackNotification[]>> => {
return os.magApi( return os.magApi(
endpoints.GetNotifications, endpoints.GetNotifications,
@ -338,7 +338,7 @@ const fetch = async (
limit: displayLimit, limit: displayLimit,
}, },
{}, {},
undefined undefined,
); );
}; };
@ -356,7 +356,7 @@ const init = async (): Promise<void> => {
(err) => { (err) => {
error.value = true; error.value = true;
initialFetching.value = false; initialFetching.value = false;
} },
); );
}; };
@ -390,7 +390,7 @@ const fetchMore = async (): Promise<void> => {
}, },
(err) => { (err) => {
moreFetching.value = false; moreFetching.value = false;
} },
); );
}; };
@ -437,7 +437,7 @@ const prepend = (item: packed.PackNotification): void => {
"note" in n && "note" in n &&
magEffectiveNote(n.note).id === magEffectiveNote(n.note).id ===
magEffectiveNote(currNotification.note).id && magEffectiveNote(currNotification.note).id &&
n.type === currNotification.type n.type === currNotification.type,
) )
) { ) {
return true; return true;
@ -522,7 +522,7 @@ watch(
if (a.length === 0 && b.length === 0) return; if (a.length === 0 && b.length === 0) return;
emit("queue", queue.value.length); emit("queue", queue.value.length);
}, },
{ deep: true } { deep: true },
); );
init(); init();

View File

@ -65,13 +65,15 @@
<script <script
lang="ts" lang="ts"
setup setup
generic="T extends BackendApiEndpoint< generic="
T['method'] & Method, T extends BackendApiEndpoint<
T['pathParams'] & string[], T['method'] & Method,
T['request'], T['pathParams'] & string[],
T['response'], T['request'],
T['paginated'] & true T['response'],
>" T['paginated'] & true
>
"
> >
import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue"; import { computed, isRef, onActivated, onDeactivated, ref, watch } from "vue";
import * as os from "@/os"; import * as os from "@/os";
@ -98,7 +100,7 @@ export type PathParams<
P["request"], P["request"],
P["response"], P["response"],
P["paginated"] & true P["paginated"] & true
> >,
> = { > = {
[K in keyof P["pathParams"] as P["pathParams"][K] & string]: [K in keyof P["pathParams"] as P["pathParams"][K] & string]:
| string | string
@ -112,7 +114,7 @@ export type Paging<
P["request"], P["request"],
P["response"], P["response"],
P["paginated"] & true P["paginated"] & true
> >,
> = { > = {
endpoint: P; endpoint: P;
pathParams: PathParams<P>; pathParams: PathParams<P>;
@ -131,7 +133,7 @@ const props = withDefaults(
}>(), }>(),
{ {
displayLimit: 30, displayLimit: 30,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -152,7 +154,7 @@ const empty = computed(() => items.value.length === 0);
const error = ref(false); const error = ref(false);
const fetch = async ( const fetch = async (
pagination: SpanFilter pagination: SpanFilter,
): Promise<PaginatedResult<T["response"]> & { data: { id: string } }[]> => { ): Promise<PaginatedResult<T["response"]> & { data: { id: string } }[]> => {
const pathParams = props.pagination.pathParams; const pathParams = props.pagination.pathParams;
const params = props.pagination.params; const params = props.pagination.params;
@ -166,12 +168,12 @@ const fetch = async (
limit: props.pagination.limit, limit: props.pagination.limit,
}, },
pathParams, pathParams,
undefined undefined,
) )
.then( .then(
(res) => (res) =>
res as PaginatedResult<T["response"]> & res as PaginatedResult<T["response"]> &
{ data: { id: string } }[] { data: { id: string } }[],
); );
}; };
@ -191,7 +193,7 @@ const init = async (): Promise<void> => {
(err) => { (err) => {
error.value = true; error.value = true;
initialFetching.value = false; initialFetching.value = false;
} },
); );
}; };
@ -222,7 +224,7 @@ const refresh = async (): Promise<void> => {
(err) => { (err) => {
error.value = true; error.value = true;
initialFetching.value = false; initialFetching.value = false;
} },
); );
}; };
@ -254,7 +256,7 @@ const fetchMore = async (): Promise<void> => {
}, },
(err) => { (err) => {
moreFetching.value = false; moreFetching.value = false;
} },
); );
}; };
@ -301,7 +303,7 @@ const prepend = (item: PageItem): void => {
queue.value = []; queue.value = [];
items.value = [...queueRemoved, ...items.value].slice( items.value = [...queueRemoved, ...items.value].slice(
0, 0,
props.displayLimit props.displayLimit,
); );
}); });
} }
@ -327,7 +329,7 @@ const removeItem = (finder: (item: PageItem) => boolean): boolean => {
const updateItem = ( const updateItem = (
id: PageItem["id"], id: PageItem["id"],
replacer: (old: PageItem) => PageItem replacer: (old: PageItem) => PageItem,
): boolean => { ): boolean => {
const i = items.value.findIndex((item) => item.id === id); const i = items.value.findIndex((item) => item.id === id);
if (i === -1) { if (i === -1) {
@ -348,7 +350,7 @@ watch(
if (a.length === 0 && b.length === 0) return; if (a.length === 0 && b.length === 0) return;
emit("queue", queue.value.length); emit("queue", queue.value.length);
}, },
{ deep: true } { deep: true },
); );
init(); init();

View File

@ -77,31 +77,31 @@ const pollRefreshing = ref(false);
const remaining = ref(-1); const remaining = ref(-1);
const total = computed(() => const total = computed(() =>
sum(props.note.poll.options.map((x) => x.votes_count)) sum(props.note.poll.options.map((x) => x.votes_count)),
); );
const closed = computed(() => remaining.value === 0); const closed = computed(() => remaining.value === 0);
const isLocal = computed(() => !props.note.uri); const isLocal = computed(() => !props.note.uri);
const isVoted = computed( const isVoted = computed(
() => () =>
!props.note.poll.multiple_choice && !props.note.poll.multiple_choice &&
props.note.poll.options.some((c) => c.voted ?? false) props.note.poll.options.some((c) => c.voted ?? false),
); );
const timer = computed(() => const timer = computed(() =>
i18n.t( i18n.t(
remaining.value >= 86400 remaining.value >= 86400
? "_poll.remainingDays" ? "_poll.remainingDays"
: remaining.value >= 3600 : remaining.value >= 3600
? "_poll.remainingHours" ? "_poll.remainingHours"
: remaining.value >= 60 : remaining.value >= 60
? "_poll.remainingMinutes" ? "_poll.remainingMinutes"
: "_poll.remainingSeconds", : "_poll.remainingSeconds",
{ {
s: Math.floor(remaining.value % 60), s: Math.floor(remaining.value % 60),
m: Math.floor(remaining.value / 60) % 60, m: Math.floor(remaining.value / 60) % 60,
h: Math.floor(remaining.value / 3600) % 24, h: Math.floor(remaining.value / 3600) % 24,
d: Math.floor(remaining.value / 86400), d: Math.floor(remaining.value / 86400),
} },
) ),
); );
const showResult = ref(props.readOnly || isVoted.value); const showResult = ref(props.readOnly || isVoted.value);
@ -112,8 +112,8 @@ if (props.note.poll.expires_at) {
remaining.value = Math.floor( remaining.value = Math.floor(
Math.max( Math.max(
new Date(props.note.poll.expires_at!).getTime() - Date.now(), new Date(props.note.poll.expires_at!).getTime() - Date.now(),
0 0,
) / 1000 ) / 1000,
); );
if (remaining.value === 0) { if (remaining.value === 0) {
showResult.value = true; showResult.value = true;
@ -137,7 +137,7 @@ async function refresh() {
os.magApi( os.magApi(
endpoints.GetNoteById, endpoints.GetNoteById,
{ attachments: true }, { attachments: true },
{ id: obj.object.id } { id: obj.object.id },
).then((n) => { ).then((n) => {
props.note.poll = { ...toRaw(props.note.poll), ...n.poll }; props.note.poll = { ...toRaw(props.note.poll), ...n.poll };
}); });

View File

@ -24,7 +24,7 @@ const props = defineProps<{
const canRenote = computed( const canRenote = computed(
() => () =>
["public", "home", "Public", "Home"].includes(props.note.visibility) || ["public", "home", "Public", "Home"].includes(props.note.visibility) ||
props.note.user.id === $i?.id props.note.user.id === $i?.id,
); );
function quote(): void { function quote(): void {

View File

@ -48,7 +48,7 @@ const hasRenotedBefore = ref<boolean>((props.note.self_renote_count ?? 0) > 0);
const canRenote = computed( const canRenote = computed(
() => () =>
["Public", "Home"].includes(props.note.visibility) || ["Public", "Home"].includes(props.note.visibility) ||
($i && props.note.user.id === $i.id) ($i && props.note.user.id === $i.id),
); );
useTooltip(buttonRef, async (showing) => { useTooltip(buttonRef, async (showing) => {
@ -70,7 +70,7 @@ useTooltip(buttonRef, async (showing) => {
targetElement: buttonRef.value, targetElement: buttonRef.value,
}, },
{}, {},
"closed" "closed",
); );
}); });
@ -198,7 +198,7 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
? { ? {
renoteId: props.note.id, renoteId: props.note.id,
visibility: magLegacyVisibility( visibility: magLegacyVisibility(
props.note.visibility props.note.visibility,
), ),
visibleUserIds: props.note.visible_user_ids ?? [], visibleUserIds: props.note.visible_user_ids ?? [],
localOnly: true, localOnly: true,
@ -206,10 +206,10 @@ const renote = async (viaKeyboard = false, ev?: MouseEvent) => {
: { : {
renoteId: props.note.id, renoteId: props.note.id,
visibility: magLegacyVisibility( visibility: magLegacyVisibility(
props.note.visibility props.note.visibility,
), ),
localOnly: true, localOnly: true,
} },
); );
hasRenotedBefore.value = true; hasRenotedBefore.value = true;
const el = const el =

View File

@ -23,7 +23,7 @@ import { MagChannelState } from "magnetar-common";
import { onMounted, onUnmounted, ref } from "vue"; import { onMounted, onUnmounted, ref } from "vue";
defineProps<{ defineProps<{
showConnected?: boolean showConnected?: boolean;
}>(); }>();
const state = ref<MagChannelState>(magStream.state); const state = ref<MagChannelState>(magStream.state);
@ -65,7 +65,8 @@ const emit = defineEmits<{
z-index: 5; z-index: 5;
top: 0; top: 0;
&.closed, &.failed { &.closed,
&.failed {
background-color: var(--infoWarnBg); background-color: var(--infoWarnBg);
color: var(--error); color: var(--error);
} }

View File

@ -218,23 +218,23 @@ const isLong =
(props.note.text.split("\n").length > 10 || (props.note.text.split("\n").length > 10 ||
props.note.text.length > 800)) || props.note.text.length > 800)) ||
(props.note.attachments ?? []).length > 4); (props.note.attachments ?? []).length > 4);
const collapsed = $ref(props.note.cw == null && isLong); const collapsed = ref(props.note.cw == null && isLong);
const urls = props.note.text const urls = props.note.text
? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5) ? extractUrlFromMfm(mfm.parse(props.note.text)).slice(0, 5)
: null; : null;
let showContent = $ref(false); let showContent = ref(false);
const mfms = props.note.text const mfms = props.note.text
? extractMfmWithAnimation(mfm.parse(props.note.text)) ? extractMfmWithAnimation(mfm.parse(props.note.text))
: null; : null;
const hasMfm = $ref(mfms && mfms.length > 0); const hasMfm = ref(mfms && mfms.length > 0);
let disableMfm = $ref(defaultStore.state.animatedMfm); let disableMfm = ref(defaultStore.state.animatedMfm);
async function toggleMfm() { async function toggleMfm() {
if (disableMfm) { if (disableMfm.value) {
if (!defaultStore.state.animatedMfmWarnShown) { if (!defaultStore.state.animatedMfmWarnShown) {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: "warning", type: "warning",
@ -245,9 +245,9 @@ async function toggleMfm() {
defaultStore.set("animatedMfmWarnShown", true); defaultStore.set("animatedMfmWarnShown", true);
} }
disableMfm = false; disableMfm.value = false;
} else { } else {
disableMfm = true; disableMfm.value = true;
} }
} }

View File

@ -127,7 +127,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onMounted } from "vue"; import { computed, onMounted, ref } from "vue";
import MkFollowButton from "@/components/MkFollowButton.vue"; import MkFollowButton from "@/components/MkFollowButton.vue";
import { userPage } from "@/filters/user"; import { userPage } from "@/filters/user";
import XShowMoreButton from "@/components/MkShowMoreButton.vue"; import XShowMoreButton from "@/components/MkShowMoreButton.vue";
@ -151,18 +151,19 @@ const emit = defineEmits<{
}>(); }>();
const zIndex = os.claimZIndex("middle"); const zIndex = os.claimZIndex("middle");
let user = $ref<packed.PackUserMaybeAll | null>(null); let user = ref<packed.PackUserMaybeAll | null>(null);
let top = $ref(0); let top = ref(0);
let left = $ref(0); let left = ref(0);
let isLong = computed(() => { let isLong = computed(() => {
if (!user?.description) return; if (!user.value?.description) return;
return ( return (
user.description.split("\n").length > 9 || user.description.length > 400 user.value.description.split("\n").length > 9 ||
user.value.description.length > 400
); );
}); });
let collapsed = $ref(!isLong); let collapsed = ref(!isLong);
onMounted(() => { onMounted(() => {
const options = { detail: true, profile: true, relation: true }; const options = { detail: true, profile: true, relation: true };
@ -176,7 +177,7 @@ onMounted(() => {
os.magApi(endpoints.GetUserByAcct, options, { os.magApi(endpoints.GetUserByAcct, options, {
user_acct: canonical, user_acct: canonical,
}).then((u) => { }).then((u) => {
user = u; user.value = u;
}); });
} else { } else {
const acctQuery = props.userTag.startsWith("@"); const acctQuery = props.userTag.startsWith("@");
@ -189,19 +190,22 @@ onMounted(() => {
user_id: props.userTag, user_id: props.userTag,
}); });
apiCall.then((u) => (user = u)); apiCall.then((u) => (user.value = u));
} }
const rect = props.source.getBoundingClientRect(); const rect = props.source.getBoundingClientRect();
left = rect.left + props.source.offsetWidth / 2 - 300 / 2 + window.scrollX; left.value =
top = rect.top + props.source.offsetHeight + window.scrollY; rect.left + props.source.offsetWidth / 2 - 300 / 2 + window.scrollX;
top.value = rect.top + props.source.offsetHeight + window.scrollY;
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.popup-enter-active, .popup-enter-active,
.popup-leave-active { .popup-leave-active {
transition: opacity 0.3s, transform 0.3s !important; transition:
opacity 0.3s,
transform 0.3s !important;
} }
.popup-enter-from, .popup-enter-from,
.popup-leave-to { .popup-leave-to {

View File

@ -0,0 +1,86 @@
<template>
<media-player
class="player"
:title="video.comment"
:src="{
src: video.url,
type: magTransProperty(video, 'mime_type', 'type'),
}"
:volume="volume"
:controlsDelay="500"
:hideOnMouseLeave="true"
:load="'play'"
crossOrigin
playsInline
@volume-change="volumeChange"
>
<media-provider>
<media-poster
:src="magTransProperty(video, 'thumbnail_url', 'thumbnailUrl')"
>
</media-poster>
</media-provider>
<media-plyr-layout
:download="{ filename: video.name, url: video.url }"
:controls="[
'play',
'current-time',
'progress',
'duration',
'mute+volume',
'settings',
'download',
'pip',
'fullscreen',
]"
/>
</media-player>
</template>
<script lang="ts" setup>
import "vidstack/player/styles/plyr/theme.css";
import "vidstack/player";
import "vidstack/player/layouts";
import "vidstack/player/ui";
import type * as misskey from "calckey-js";
import { packed } from "magnetar-common";
import { magTransProperty } from "@/scripts-mag/mag-util";
import { ColdDeviceStorage } from "@/store";
import { ref } from "vue";
defineProps<{
video: packed.PackDriveFileBase | misskey.entities.DriveFile;
}>();
const volume = ref(ColdDeviceStorage.get("mediaVolume"));
function volumeChange(e: Event): void {
if (e?.target?.volume)
ColdDeviceStorage.set("mediaVolume", e.target.volume);
}
</script>
<style lang="scss">
.player {
--plyr-color-main: var(--accent);
&[data-view-type="video"] {
aspect-ratio: 16 / 9;
}
video {
width: 100%;
height: 100%;
}
& .plyr--video {
& .plyr__controls {
transition: opacity 0.2s ease-in-out;
}
&.plyr--hide-controls .plyr__controls {
transform: unset;
}
}
}
</style>

View File

@ -70,6 +70,7 @@ import MkSwitch from "@/components/form/switch.vue";
import MkKeyValue from "@/components/MkKeyValue.vue"; import MkKeyValue from "@/components/MkKeyValue.vue";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { ref } from "vue";
const props = defineProps<{ const props = defineProps<{
report: any; report: any;
@ -79,11 +80,11 @@ const emit = defineEmits<{
(ev: "resolved", reportId: string): void; (ev: "resolved", reportId: string): void;
}>(); }>();
let forward = $ref(props.report.forwarded); let forward = ref(props.report.forwarded);
function resolve() { function resolve() {
os.apiWithDialog("admin/resolve-abuse-user-report", { os.apiWithDialog("admin/resolve-abuse-user-report", {
forward: forward, forward: forward.value,
reportId: props.report.id, reportId: props.report.id,
}).then(() => { }).then(() => {
emit("resolved", props.report.id); emit("resolved", props.report.id);

View File

@ -67,7 +67,7 @@ function send() {
userId: props.user.id, userId: props.user.id,
comment: comment.value, comment: comment.value,
}, },
undefined undefined,
).then((res) => { ).then((res) => {
os.alert({ os.alert({
type: "success", type: "success",

View File

@ -19,7 +19,7 @@
0, 0,
1 - 1 -
angleDiff(hAngle, angle) / Math.PI - angleDiff(hAngle, angle) / Math.PI -
numbersOpacityFactor numbersOpacityFactor,
) )
" "
/> />
@ -48,7 +48,7 @@
0, 0,
1 - 1 -
angleDiff(hAngle, angle) / Math.PI - angleDiff(hAngle, angle) / Math.PI -
numbersOpacityFactor numbersOpacityFactor,
) )
" "
> >
@ -108,14 +108,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { import { ref, computed, onMounted, onBeforeUnmount } from "vue";
ref,
computed,
onMounted,
onBeforeUnmount,
shallowRef,
nextTick,
} from "vue";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import { globalEvents } from "@/events.js"; import { globalEvents } from "@/events.js";
@ -151,7 +144,7 @@ const props = withDefaults(
graduations: "dots", graduations: "dots",
fadeGraduations: true, fadeGraduations: true,
sAnimation: "elastic", sAnimation: "elastic",
} },
); );
const graduationsMajor = computed(() => { const graduationsMajor = computed(() => {
@ -174,49 +167,51 @@ const texts = computed(() => {
}); });
let enabled = true; let enabled = true;
let majorGraduationColor = $ref<string>(); let majorGraduationColor = ref<string>();
//let minorGraduationColor = $ref<string>(); //let minorGraduationColor = $ref<string>();
let sHandColor = $ref<string>(); let sHandColor = ref<string>();
let mHandColor = $ref<string>(); let mHandColor = ref<string>();
let hHandColor = $ref<string>(); let hHandColor = ref<string>();
let nowColor = $ref<string>(); let nowColor = ref<string>();
let h = $ref<number>(0); let h = ref<number>(0);
let m = $ref<number>(0); let m = ref<number>(0);
let s = $ref<number>(0); let s = ref<number>(0);
let hAngle = $ref<number>(0); let hAngle = ref<number>(0);
let mAngle = $ref<number>(0); let mAngle = ref<number>(0);
let sAngle = $ref<number>(0); let sAngle = ref<number>(0);
let disableSAnimate = $ref(false); let disableSAnimate = ref(false);
let sOneRound = false; let sOneRound = false;
function tick() { function tick() {
const now = new Date(); const now = new Date();
now.setMinutes( now.setMinutes(
now.getMinutes() + (new Date().getTimezoneOffset() + props.offset) now.getMinutes() + (new Date().getTimezoneOffset() + props.offset),
); );
s = now.getSeconds(); s.value = now.getSeconds();
m = now.getMinutes(); m.value = now.getMinutes();
h = now.getHours(); h.value = now.getHours();
hAngle = hAngle.value =
(Math.PI * ((h % (props.twentyfour ? 24 : 12)) + (m + s / 60) / 60)) / (Math.PI *
((h.value % (props.twentyfour ? 24 : 12)) +
(m.value + s.value / 60) / 60)) /
(props.twentyfour ? 12 : 6); (props.twentyfour ? 12 : 6);
mAngle = (Math.PI * (m + s / 60)) / 30; mAngle.value = (Math.PI * (m.value + s.value / 60)) / 30;
if (sOneRound) { if (sOneRound) {
// (59->0) // (59->0)
sAngle = (Math.PI * 60) / 30; sAngle.value = (Math.PI * 60) / 30;
window.setTimeout(() => { window.setTimeout(() => {
disableSAnimate = true; disableSAnimate.value = true;
window.setTimeout(() => { window.setTimeout(() => {
sAngle = 0; sAngle.value = 0;
window.setTimeout(() => { window.setTimeout(() => {
disableSAnimate = false; disableSAnimate.value = false;
}, 100); }, 100);
}, 100); }, 100);
}, 700); }, 700);
} else { } else {
sAngle = (Math.PI * s) / 30; sAngle.value = (Math.PI * s.value) / 30;
} }
sOneRound = s === 59; sOneRound = s.value === 59;
} }
tick(); tick();
@ -225,18 +220,18 @@ function calcColors() {
const computedStyle = getComputedStyle(document.documentElement); const computedStyle = getComputedStyle(document.documentElement);
const dark = tinycolor(computedStyle.getPropertyValue("--bg")).isDark(); const dark = tinycolor(computedStyle.getPropertyValue("--bg")).isDark();
const accent = tinycolor( const accent = tinycolor(
computedStyle.getPropertyValue("--accent") computedStyle.getPropertyValue("--accent"),
).toHexString(); ).toHexString();
majorGraduationColor = dark majorGraduationColor.value = dark
? "rgba(255, 255, 255, 0.3)" ? "rgba(255, 255, 255, 0.3)"
: "rgba(0, 0, 0, 0.3)"; : "rgba(0, 0, 0, 0.3)";
//minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; //minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
sHandColor = dark ? "rgba(255, 255, 255, 0.5)" : "rgba(0, 0, 0, 0.3)"; sHandColor.value = dark ? "rgba(255, 255, 255, 0.5)" : "rgba(0, 0, 0, 0.3)";
mHandColor = tinycolor( mHandColor.value = tinycolor(
computedStyle.getPropertyValue("--fg") computedStyle.getPropertyValue("--fg"),
).toHexString(); ).toHexString();
hHandColor = accent; hHandColor.value = accent;
nowColor = accent; nowColor.value = accent;
} }
calcColors(); calcColors();

View File

@ -268,7 +268,7 @@ function exec() {
} else if (props.type === "hashtag") { } else if (props.type === "hashtag") {
if (!props.q || props.q === "") { if (!props.q || props.q === "") {
hashtags.value = JSON.parse( hashtags.value = JSON.parse(
localStorage.getItem("hashtags") || "[]" localStorage.getItem("hashtags") || "[]",
); );
fetching.value = false; fetching.value = false;
} else { } else {
@ -288,7 +288,7 @@ function exec() {
// //
sessionStorage.setItem( sessionStorage.setItem(
cacheKey, cacheKey,
JSON.stringify(searchedHashtags) JSON.stringify(searchedHashtags),
); );
}); });
} }
@ -298,7 +298,7 @@ function exec() {
// 使 // 使
emojis.value = defaultStore.state.recentlyUsedEmojis emojis.value = defaultStore.state.recentlyUsedEmojis
.map((emoji) => .map((emoji) =>
emojiDb.find((dbEmoji) => dbEmoji.emoji === emoji) emojiDb.find((dbEmoji) => dbEmoji.emoji === emoji),
) )
.filter((x) => x) as EmojiDef[]; .filter((x) => x) as EmojiDef[];
return; return;
@ -450,7 +450,7 @@ onMounted(() => {
nextTick(() => { nextTick(() => {
exec(); exec();
}); });
} },
); );
}); });
}); });
@ -470,7 +470,9 @@ onBeforeUnmount(() => {
max-width: 100%; max-width: 100%;
margin-top: calc(1em + 8px); margin-top: calc(1em + 8px);
overflow: hidden; overflow: hidden;
transition: top 0.1s ease, left 0.1s ease; transition:
top 0.1s ease,
left 0.1s ease;
> ol { > ol {
display: block; display: block;

View File

@ -27,7 +27,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted } from "vue"; import { nextTick, onMounted, ref } from "vue";
const props = defineProps<{ const props = defineProps<{
type?: "button" | "submit" | "reset"; type?: "button" | "submit" | "reset";
@ -49,22 +49,30 @@ const emit = defineEmits<{
(ev: "click", payload: MouseEvent): void; (ev: "click", payload: MouseEvent): void;
}>(); }>();
let el = $ref<HTMLElement | null>(null); let el = ref<HTMLElement | null>(null);
let ripples = $ref<HTMLElement | null>(null); let ripples = ref<HTMLElement | null>(null);
onMounted(() => { onMounted(() => {
if (props.autofocus) { if (props.autofocus) {
nextTick(() => { nextTick(() => {
el!.focus(); el.value!.focus();
}); });
} }
}); });
function distance(p, q): number { function distance(
p: { x: number; y: number },
q: { x: number; y: number },
): number {
return Math.hypot(p.x - q.x, p.y - q.y); return Math.hypot(p.x - q.x, p.y - q.y);
} }
function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY): number { function calcCircleScale(
boxW: number,
boxH: number,
circleCenterX: number,
circleCenterY: number,
): number {
const origin = { x: circleCenterX, y: circleCenterY }; const origin = { x: circleCenterX, y: circleCenterY };
const dist1 = distance({ x: 0, y: 0 }, origin); const dist1 = distance({ x: 0, y: 0 }, origin);
const dist2 = distance({ x: boxW, y: 0 }, origin); const dist2 = distance({ x: boxW, y: 0 }, origin);
@ -81,7 +89,7 @@ function onMousedown(evt: MouseEvent): void {
ripple.style.top = (evt.clientY - rect.top - 1).toString() + "px"; ripple.style.top = (evt.clientY - rect.top - 1).toString() + "px";
ripple.style.left = (evt.clientX - rect.left - 1).toString() + "px"; ripple.style.left = (evt.clientX - rect.left - 1).toString() + "px";
ripples!.appendChild(ripple); ripples.value!.appendChild(ripple);
const circleCenterX = evt.clientX - rect.left; const circleCenterX = evt.clientX - rect.left;
const circleCenterY = evt.clientY - rect.top; const circleCenterY = evt.clientY - rect.top;
@ -90,7 +98,7 @@ function onMousedown(evt: MouseEvent): void {
target.clientWidth, target.clientWidth,
target.clientHeight, target.clientHeight,
circleCenterX, circleCenterX,
circleCenterY circleCenterY,
); );
window.setTimeout(() => { window.setTimeout(() => {
@ -101,7 +109,7 @@ function onMousedown(evt: MouseEvent): void {
ripple.style.opacity = "0"; ripple.style.opacity = "0";
}, 1000); }, 1000);
window.setTimeout(() => { window.setTimeout(() => {
if (ripples) ripples.removeChild(ripple); if (ripples) ripples.value!.removeChild(ripple);
}, 2000); }, 2000);
} }
</script> </script>

View File

@ -25,7 +25,7 @@ type Captcha = {
| "expired-callback" | "expired-callback"
| "error-callback" | "error-callback"
| "endpoint"]?: unknown; | "endpoint"]?: unknown;
} },
): string; ): string;
remove(id: string): void; remove(id: string): void;
execute(id: string): void; execute(id: string): void;
@ -78,7 +78,7 @@ const src = computed(() => {
}); });
const captcha = computed<Captcha>( const captcha = computed<Captcha>(
() => window[variable.value] || ({} as unknown as Captcha) () => window[variable.value] || ({} as unknown as Captcha),
); );
if (loaded) { if (loaded) {
@ -91,7 +91,7 @@ if (loaded) {
async: true, async: true,
id: props.provider, id: props.provider,
src: src.value, src: src.value,
}) }),
) )
).addEventListener("load", () => (available.value = true)); ).addEventListener("load", () => (available.value = true));
} }

View File

@ -8,7 +8,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, watch, PropType, onUnmounted } from "vue"; import { onMounted, ref, watch, PropType } from "vue";
import { import {
Chart, Chart,
ArcElement, ArcElement,
@ -91,7 +91,7 @@ Chart.register(
Tooltip, Tooltip,
SubTitle, SubTitle,
Filler, Filler,
zoomPlugin zoomPlugin,
//gradient, //gradient,
); );
@ -179,11 +179,11 @@ const render = () => {
// //
Chart.defaults.color = getComputedStyle( Chart.defaults.color = getComputedStyle(
document.documentElement document.documentElement,
).getPropertyValue("--fg"); ).getPropertyValue("--fg");
const maxes = chartData.series.map((x, i) => const maxes = chartData.series.map((x, i) =>
Math.max(...x.data.map((d) => d.y)) Math.max(...x.data.map((d) => d.y)),
); );
chartInstance = new Chart(chartEl.value, { chartInstance = new Chart(chartEl.value, {
@ -471,9 +471,9 @@ const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
raw.local.inc, raw.local.inc,
negate(raw.local.dec), negate(raw.local.dec),
raw.remote.inc, raw.remote.inc,
negate(raw.remote.dec) negate(raw.remote.dec),
) )
: sum(raw[type].inc, negate(raw[type].dec)) : sum(raw[type].inc, negate(raw[type].dec)),
), ),
color: "#888888", color: "#888888",
}, },
@ -483,7 +483,7 @@ const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
data: format( data: format(
type === "combined" type === "combined"
? sum(raw.local.diffs.renote, raw.remote.diffs.renote) ? sum(raw.local.diffs.renote, raw.remote.diffs.renote)
: raw[type].diffs.renote : raw[type].diffs.renote,
), ),
color: colors.green, color: colors.green,
}, },
@ -493,7 +493,7 @@ const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
data: format( data: format(
type === "combined" type === "combined"
? sum(raw.local.diffs.reply, raw.remote.diffs.reply) ? sum(raw.local.diffs.reply, raw.remote.diffs.reply)
: raw[type].diffs.reply : raw[type].diffs.reply,
), ),
color: colors.yellow, color: colors.yellow,
}, },
@ -503,7 +503,7 @@ const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
data: format( data: format(
type === "combined" type === "combined"
? sum(raw.local.diffs.normal, raw.remote.diffs.normal) ? sum(raw.local.diffs.normal, raw.remote.diffs.normal)
: raw[type].diffs.normal : raw[type].diffs.normal,
), ),
color: colors.blue, color: colors.blue,
}, },
@ -514,9 +514,9 @@ const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
type === "combined" type === "combined"
? sum( ? sum(
raw.local.diffs.withFile, raw.local.diffs.withFile,
raw.remote.diffs.withFile raw.remote.diffs.withFile,
) )
: raw[type].diffs.withFile : raw[type].diffs.withFile,
), ),
color: colors.purple, color: colors.purple,
}, },
@ -567,8 +567,8 @@ const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
raw.local.inc, raw.local.inc,
negate(raw.local.dec), negate(raw.local.dec),
raw.remote.inc, raw.remote.inc,
negate(raw.remote.dec) negate(raw.remote.dec),
) ),
), ),
}, },
{ {
@ -577,7 +577,7 @@ const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
data: format( data: format(
total total
? raw.local.total ? raw.local.total
: sum(raw.local.inc, negate(raw.local.dec)) : sum(raw.local.inc, negate(raw.local.dec)),
), ),
}, },
{ {
@ -586,7 +586,7 @@ const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
data: format( data: format(
total total
? raw.remote.total ? raw.remote.total
: sum(raw.remote.inc, negate(raw.remote.dec)) : sum(raw.remote.inc, negate(raw.remote.dec)),
), ),
}, },
], ],
@ -675,8 +675,8 @@ const fetchDriveChart = async (): Promise<typeof chartData> => {
raw.local.incSize, raw.local.incSize,
negate(raw.local.decSize), negate(raw.local.decSize),
raw.remote.incSize, raw.remote.incSize,
negate(raw.remote.decSize) negate(raw.remote.decSize),
) ),
), ),
}, },
{ {
@ -719,8 +719,8 @@ const fetchDriveFilesChart = async (): Promise<typeof chartData> => {
raw.local.incCount, raw.local.incCount,
negate(raw.local.decCount), negate(raw.local.decCount),
raw.remote.incCount, raw.remote.incCount,
negate(raw.remote.decCount) negate(raw.remote.decCount),
) ),
), ),
}, },
{ {
@ -778,7 +778,7 @@ const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
}; };
const fetchInstanceUsersChart = async ( const fetchInstanceUsersChart = async (
total: boolean total: boolean,
): Promise<typeof chartData> => { ): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", { const raw = await os.apiGet("charts/instance", {
host: props.args.host, host: props.args.host,
@ -794,7 +794,7 @@ const fetchInstanceUsersChart = async (
data: format( data: format(
total total
? raw.users.total ? raw.users.total
: sum(raw.users.inc, negate(raw.users.dec)) : sum(raw.users.inc, negate(raw.users.dec)),
), ),
}, },
], ],
@ -802,7 +802,7 @@ const fetchInstanceUsersChart = async (
}; };
const fetchInstanceNotesChart = async ( const fetchInstanceNotesChart = async (
total: boolean total: boolean,
): Promise<typeof chartData> => { ): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", { const raw = await os.apiGet("charts/instance", {
host: props.args.host, host: props.args.host,
@ -818,7 +818,7 @@ const fetchInstanceNotesChart = async (
data: format( data: format(
total total
? raw.notes.total ? raw.notes.total
: sum(raw.notes.inc, negate(raw.notes.dec)) : sum(raw.notes.inc, negate(raw.notes.dec)),
), ),
}, },
], ],
@ -826,7 +826,7 @@ const fetchInstanceNotesChart = async (
}; };
const fetchInstanceFfChart = async ( const fetchInstanceFfChart = async (
total: boolean total: boolean,
): Promise<typeof chartData> => { ): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", { const raw = await os.apiGet("charts/instance", {
host: props.args.host, host: props.args.host,
@ -842,7 +842,7 @@ const fetchInstanceFfChart = async (
data: format( data: format(
total total
? raw.following.total ? raw.following.total
: sum(raw.following.inc, negate(raw.following.dec)) : sum(raw.following.inc, negate(raw.following.dec)),
), ),
}, },
{ {
@ -852,7 +852,7 @@ const fetchInstanceFfChart = async (
data: format( data: format(
total total
? raw.followers.total ? raw.followers.total
: sum(raw.followers.inc, negate(raw.followers.dec)) : sum(raw.followers.inc, negate(raw.followers.dec)),
), ),
}, },
], ],
@ -860,7 +860,7 @@ const fetchInstanceFfChart = async (
}; };
const fetchInstanceDriveUsageChart = async ( const fetchInstanceDriveUsageChart = async (
total: boolean total: boolean,
): Promise<typeof chartData> => { ): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", { const raw = await os.apiGet("charts/instance", {
host: props.args.host, host: props.args.host,
@ -877,7 +877,7 @@ const fetchInstanceDriveUsageChart = async (
data: format( data: format(
total total
? raw.drive.totalUsage ? raw.drive.totalUsage
: sum(raw.drive.incUsage, negate(raw.drive.decUsage)) : sum(raw.drive.incUsage, negate(raw.drive.decUsage)),
), ),
}, },
], ],
@ -885,7 +885,7 @@ const fetchInstanceDriveUsageChart = async (
}; };
const fetchInstanceDriveFilesChart = async ( const fetchInstanceDriveFilesChart = async (
total: boolean total: boolean,
): Promise<typeof chartData> => { ): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", { const raw = await os.apiGet("charts/instance", {
host: props.args.host, host: props.args.host,
@ -901,7 +901,7 @@ const fetchInstanceDriveFilesChart = async (
data: format( data: format(
total total
? raw.drive.totalFiles ? raw.drive.totalFiles
: sum(raw.drive.incFiles, negate(raw.drive.decFiles)) : sum(raw.drive.incFiles, negate(raw.drive.decFiles)),
), ),
}, },
], ],

View File

@ -11,6 +11,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
import XCheatSheet from "@/pages/mfm-cheat-sheet.vue"; import XCheatSheet from "@/pages/mfm-cheat-sheet.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -20,10 +22,10 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
const dialog = $ref<InstanceType<typeof XModalWindow>>(); const dialog = ref<InstanceType<typeof XModalWindow>>();
function close(res) { function close(res) {
dialog.close(); dialog.value.close();
} }
</script> </script>

View File

@ -18,13 +18,13 @@ const props = defineProps<{
}>(); }>();
const prismLang = computed(() => const prismLang = computed(() =>
Prism.languages[props.lang] ? props.lang : "js" Prism.languages[props.lang] ? props.lang : "js",
); );
const html = computed(() => const html = computed(() =>
Prism.highlight( Prism.highlight(
props.code, props.code,
Prism.languages[prismLang.value], Prism.languages[prismLang.value],
prismLang.value prismLang.value,
) ),
); );
</script> </script>

View File

@ -12,6 +12,6 @@ defineProps<{
}>(); }>();
const XCode = defineAsyncComponent( const XCode = defineAsyncComponent(
() => import("@/components/MkCode.core.vue") () => import("@/components/MkCode.core.vue"),
); );
</script> </script>

View File

@ -125,7 +125,7 @@ export default defineComponent({
}, },
{ {
immediate: true, immediate: true,
} },
); );
this.$el.style.setProperty("--maxHeight", this.maxHeight + "px"); this.$el.style.setProperty("--maxHeight", this.maxHeight + "px");
@ -174,7 +174,9 @@ export default defineComponent({
.container-toggle-enter-active, .container-toggle-enter-active,
.container-toggle-leave-active { .container-toggle-leave-active {
overflow-y: hidden; overflow-y: hidden;
transition: opacity 0.5s, height 0.5s !important; transition:
opacity 0.5s,
height 0.5s !important;
} }
.container-toggle-enter-from { .container-toggle-enter-from {
opacity: 0; opacity: 0;

View File

@ -12,7 +12,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onBeforeUnmount } from "vue"; import { onMounted, onBeforeUnmount, ref } from "vue";
import MkMenu from "./MkMenu.vue"; import MkMenu from "./MkMenu.vue";
import { MenuItem } from "./types/menu.vue"; import { MenuItem } from "./types/menu.vue";
import contains from "@/scripts/contains"; import contains from "@/scripts/contains";
@ -27,16 +27,16 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let rootEl = $ref<HTMLDivElement>(); let rootEl = ref<HTMLDivElement>();
let zIndex = $ref<number>(os.claimZIndex("high")); let zIndex = ref<number>(os.claimZIndex("high"));
onMounted(() => { onMounted(() => {
let left = props.ev.pageX + 1; // + 1 let left = props.ev.pageX + 1; // + 1
let top = props.ev.pageY + 1; // + 1 let top = props.ev.pageY + 1; // + 1
const width = rootEl.offsetWidth; const width = rootEl.value.offsetWidth;
const height = rootEl.offsetHeight; const height = rootEl.value.offsetHeight;
if (left + width - window.pageXOffset > window.innerWidth) { if (left + width - window.pageXOffset > window.innerWidth) {
left = window.innerWidth - width + window.pageXOffset; left = window.innerWidth - width + window.pageXOffset;
@ -54,8 +54,8 @@ onMounted(() => {
left = 0; left = 0;
} }
rootEl.style.top = `${top}px`; rootEl.value.style.top = `${top}px`;
rootEl.style.left = `${left}px`; rootEl.value.style.left = `${left}px`;
for (const el of Array.from(document.querySelectorAll("body *"))) { for (const el of Array.from(document.querySelectorAll("body *"))) {
el.addEventListener("mousedown", onMousedown); el.addEventListener("mousedown", onMousedown);
@ -69,7 +69,8 @@ onBeforeUnmount(() => {
}); });
function onMousedown(evt: Event) { function onMousedown(evt: Event) {
if (!contains(rootEl, evt.target) && rootEl !== evt.target) emit("closed"); if (!contains(rootEl.value, evt.target) && rootEl.value !== evt.target)
emit("closed");
} }
</script> </script>
@ -80,7 +81,8 @@ function onMousedown(evt: Event) {
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transition:
opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
transform-origin: left top; transform-origin: left top;
} }

View File

@ -34,7 +34,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted } from "vue"; import { nextTick, onMounted, ref } from "vue";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import Cropper from "cropperjs"; import Cropper from "cropperjs";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
@ -60,10 +60,10 @@ const props = defineProps<{
const imgUrl = `${url}/proxy/image.webp?${query({ const imgUrl = `${url}/proxy/image.webp?${query({
url: props.file.url, url: props.file.url,
})}`; })}`;
let dialogEl = $ref<InstanceType<typeof XModalWindow>>(); let dialogEl = ref<InstanceType<typeof XModalWindow>>();
let imgEl = $ref<HTMLImageElement>(); let imgEl = ref<HTMLImageElement>();
let cropper: Cropper | null = null; let cropper: Cropper | null = null;
let loading = $ref(true); let loading = ref(true);
const ok = async () => { const ok = async () => {
const promise = new Promise<misskey.entities.DriveFile>(async (res) => { const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
@ -94,16 +94,16 @@ const ok = async () => {
const f = await promise; const f = await promise;
emit("ok", f); emit("ok", f);
dialogEl.close(); dialogEl.value.close();
}; };
const cancel = () => { const cancel = () => {
emit("cancel"); emit("cancel");
dialogEl.close(); dialogEl.value.close();
}; };
const onImageLoad = () => { const onImageLoad = () => {
loading = false; loading.value = false;
if (cropper) { if (cropper) {
cropper.getCropperImage()!.$center("contain"); cropper.getCropperImage()!.$center("contain");
@ -112,13 +112,13 @@ const onImageLoad = () => {
}; };
onMounted(() => { onMounted(() => {
cropper = new Cropper(imgEl, {}); cropper = new Cropper(imgEl.value, {});
const computedStyle = getComputedStyle(document.documentElement); const computedStyle = getComputedStyle(document.documentElement);
const selection = cropper.getCropperSelection()!; const selection = cropper.getCropperSelection()!;
selection.themeColor = tinycolor( selection.themeColor = tinycolor(
computedStyle.getPropertyValue("--accent") computedStyle.getPropertyValue("--accent"),
).toHexString(); ).toHexString();
selection.aspectRatio = props.aspectRatio; selection.aspectRatio = props.aspectRatio;
selection.initialAspectRatio = props.aspectRatio; selection.initialAspectRatio = props.aspectRatio;

View File

@ -68,7 +68,9 @@ defineExpose({
> span { > span {
background: var(--cwBg) !important; background: var(--cwBg) !important;
color: var(--cwFg); color: var(--cwFg);
transition: background 0.2s, color 0.2s; transition:
background 0.2s,
color 0.2s;
> span { > span {
font-weight: 500; font-weight: 500;
&::before { &::before {

View File

@ -78,14 +78,24 @@
okButtonDisabled && okButtonDisabled &&
disabledReason === 'charactersExceeded' disabledReason === 'charactersExceeded'
" "
v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })" v-text="
i18n.t('_dialog.charactersExceeded', {
current: (inputValue as string).length,
max: input.maxLength ?? 'NaN',
})
"
/> />
<span <span
v-else-if=" v-else-if="
okButtonDisabled && okButtonDisabled &&
disabledReason === 'charactersBelow' disabledReason === 'charactersBelow'
" "
v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })" v-text="
i18n.t('_dialog.charactersBelow', {
current: (inputValue as string).length,
min: input.minLength ?? 'NaN',
})
"
/> />
</template> </template>
<template v-if="input.type === 'search'" #suffix> <template v-if="input.type === 'search'" #suffix>
@ -189,7 +199,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef } from "vue"; import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from "vue";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
@ -258,7 +268,7 @@ const props = withDefaults(
isYesNo: false, isYesNo: false,
cancelableByBgClick: true, cancelableByBgClick: true,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -271,17 +281,15 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const inputValue = ref<string | number | null>(props.input?.default ?? null); const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null); const selectedValue = ref(props.select?.default ?? null);
let disabledReason = $ref<null | "charactersExceeded" | "charactersBelow">( let disabledReason = ref<null | "charactersExceeded" | "charactersBelow">(null);
null const okButtonDisabled = computed<boolean>(() => {
);
const okButtonDisabled = $computed<boolean>(() => {
if (props.input) { if (props.input) {
if (props.input.minLength) { if (props.input.minLength) {
if ( if (
(inputValue.value || inputValue.value === "") && (inputValue.value || inputValue.value === "") &&
(inputValue.value as string).length < props.input.minLength (inputValue.value as string).length < props.input.minLength
) { ) {
disabledReason = "charactersBelow"; disabledReason.value = "charactersBelow";
return true; return true;
} }
} }
@ -290,7 +298,7 @@ const okButtonDisabled = $computed<boolean>(() => {
inputValue.value && inputValue.value &&
(inputValue.value as string).length > props.input.maxLength (inputValue.value as string).length > props.input.maxLength
) { ) {
disabledReason = "charactersExceeded"; disabledReason.value = "charactersExceeded";
return true; return true;
} }
} }
@ -311,8 +319,8 @@ async function ok() {
const result = props.input const result = props.input
? inputValue.value ? inputValue.value
: props.select : props.select
? selectedValue.value ? selectedValue.value
: true; : true;
done(false, result); done(false, result);
} }
@ -439,7 +447,7 @@ async function openSearchFilters(ev) {
}, },
], ],
ev.target, ev.target,
{ noReturnFocus: true } { noReturnFocus: true },
); );
inputEl.value.focus(); inputEl.value.focus();
inputEl.value.selectRange(inputValue.value.length, inputValue.value.length); // cursor at end inputEl.value.selectRange(inputValue.value.length, inputValue.value.length); // cursor at end

View File

@ -23,7 +23,7 @@ const props = withDefaults(
showS: true, showS: true,
showMs: false, showMs: false,
offset: 0 - new Date().getTimezoneOffset(), offset: 0 - new Date().getTimezoneOffset(),
} },
); );
let intervalId; let intervalId;
@ -45,7 +45,7 @@ watch(showColon, (v) => {
const tick = () => { const tick = () => {
const now = new Date(); const now = new Date();
now.setMinutes( now.setMinutes(
now.getMinutes() + (new Date().getTimezoneOffset() + props.offset) now.getMinutes() + (new Date().getTimezoneOffset() + props.offset),
); );
hh.value = now.getHours().toString().padStart(2, "0"); hh.value = now.getHours().toString().padStart(2, "0");
mm.value = now.getMinutes().toString().padStart(2, "0"); mm.value = now.getMinutes().toString().padStart(2, "0");
@ -65,7 +65,7 @@ watch(
if (intervalId) window.clearInterval(intervalId); if (intervalId) window.clearInterval(intervalId);
intervalId = window.setInterval(tick, props.showMs ? 10 : 1000); intervalId = window.setInterval(tick, props.showMs ? 10 : 1000);
}, },
{ immediate: true } { immediate: true },
); );
onUnmounted(() => { onUnmounted(() => {

View File

@ -56,7 +56,7 @@ const props = withDefaults(
{ {
isSelected: false, isSelected: false,
selectMode: false, selectMode: false,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -68,7 +68,7 @@ const emit = defineEmits<{
const isDragging = ref(false); const isDragging = ref(false);
const title = computed( const title = computed(
() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}` () => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`,
); );
function getMenu() { function getMenu() {
@ -124,7 +124,7 @@ function onClick(ev: MouseEvent) {
getMenu(), getMenu(),
(ev.currentTarget ?? ev.target ?? undefined) as (ev.currentTarget ?? ev.target ?? undefined) as
| HTMLElement | HTMLElement
| undefined | undefined,
); );
} }
} }
@ -138,7 +138,7 @@ function onDragstart(ev: DragEvent) {
ev.dataTransfer.effectAllowed = "move"; ev.dataTransfer.effectAllowed = "move";
ev.dataTransfer.setData( ev.dataTransfer.setData(
_DATA_TRANSFER_DRIVE_FILE_, _DATA_TRANSFER_DRIVE_FILE_,
JSON.stringify(props.file) JSON.stringify(props.file),
); );
} }
isDragging.value = true; isDragging.value = true;
@ -186,7 +186,7 @@ function describe() {
}); });
}, },
}, },
"closed" "closed",
); );
} }

View File

@ -52,7 +52,7 @@ const props = withDefaults(
{ {
isSelected: false, isSelected: false,
selectMode: false, selectMode: false,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -184,7 +184,7 @@ function onDragstart(ev: DragEvent) {
ev.dataTransfer.effectAllowed = "move"; ev.dataTransfer.effectAllowed = "move";
ev.dataTransfer.setData( ev.dataTransfer.setData(
_DATA_TRANSFER_DRIVE_FOLDER_, _DATA_TRANSFER_DRIVE_FOLDER_,
JSON.stringify(props.folder) JSON.stringify(props.folder),
); );
isDragging.value = true; isDragging.value = true;
@ -256,13 +256,13 @@ function onContextmenu(ev: MouseEvent) {
action: () => { action: () => {
os.popup( os.popup(
defineAsyncComponent( defineAsyncComponent(
() => import("@/components/MkDriveWindow.vue") () => import("@/components/MkDriveWindow.vue"),
), ),
{ {
initialFolder: props.folder, initialFolder: props.folder,
}, },
{}, {},
"closed" "closed",
); );
}, },
}, },
@ -280,7 +280,7 @@ function onContextmenu(ev: MouseEvent) {
action: deleteFolder, action: deleteFolder,
}, },
], ],
ev ev,
); );
} }
</script> </script>

View File

@ -29,7 +29,7 @@ const emit = defineEmits<{
( (
ev: "upload", ev: "upload",
file: File, file: File,
folder?: Misskey.entities.DriveFolder | null folder?: Misskey.entities.DriveFolder | null,
): void; ): void;
(ev: "removeFile", v: Misskey.entities.DriveFile["id"]): void; (ev: "removeFile", v: Misskey.entities.DriveFile["id"]): void;
(ev: "removeFolder", v: Misskey.entities.DriveFolder["id"]): void; (ev: "removeFolder", v: Misskey.entities.DriveFolder["id"]): void;

View File

@ -161,17 +161,17 @@ const props = withDefaults(
{ {
multiple: false, multiple: false,
select: null, select: null,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
( (
ev: "selected", ev: "selected",
v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder,
): void; ): void;
( (
ev: "change-selection", ev: "change-selection",
v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[] v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[],
): void; ): void;
(ev: "move-root"): void; (ev: "move-root"): void;
(ev: "cd", v: Misskey.entities.DriveFolder | null): void; (ev: "cd", v: Misskey.entities.DriveFolder | null): void;
@ -207,7 +207,7 @@ const ilFilesObserver = new IntersectionObserver(
entries.some((entry) => entry.isIntersecting) && entries.some((entry) => entry.isIntersecting) &&
!fetching.value && !fetching.value &&
moreFiles.value && moreFiles.value &&
fetchMoreFiles() fetchMoreFiles(),
); );
watch(folder, () => emit("cd", folder.value)); watch(folder, () => emit("cd", folder.value));
@ -230,13 +230,13 @@ function onStreamDriveFileDeleted(fileId: string) {
} }
function onStreamDriveFolderCreated( function onStreamDriveFolderCreated(
createdFolder: Misskey.entities.DriveFolder createdFolder: Misskey.entities.DriveFolder,
) { ) {
addFolder(createdFolder, true); addFolder(createdFolder, true);
} }
function onStreamDriveFolderUpdated( function onStreamDriveFolderUpdated(
updatedFolder: Misskey.entities.DriveFolder updatedFolder: Misskey.entities.DriveFolder,
) { ) {
const current = folder.value ? folder.value.id : null; const current = folder.value ? folder.value.id : null;
if (current !== updatedFolder.parentId) { if (current !== updatedFolder.parentId) {
@ -433,7 +433,7 @@ function onChangeFileInput() {
function upload( function upload(
file: File, file: File,
folderToUpload?: Misskey.entities.DriveFolder | null folderToUpload?: Misskey.entities.DriveFolder | null,
) { ) {
uploadFile( uploadFile(
file, file,
@ -441,7 +441,7 @@ function upload(
? folderToUpload.id ? folderToUpload.id
: null, : null,
undefined, undefined,
keepOriginal.value keepOriginal.value,
).then((res) => { ).then((res) => {
addFile(res, true); addFile(res, true);
}); });
@ -452,7 +452,7 @@ function chooseFile(file: Misskey.entities.DriveFile) {
if (props.multiple) { if (props.multiple) {
if (isAlreadySelected) { if (isAlreadySelected) {
selectedFiles.value = selectedFiles.value.filter( selectedFiles.value = selectedFiles.value.filter(
(f) => f.id !== file.id (f) => f.id !== file.id,
); );
} else { } else {
selectedFiles.value.push(file); selectedFiles.value.push(file);
@ -470,12 +470,12 @@ function chooseFile(file: Misskey.entities.DriveFile) {
function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) { function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) {
const isAlreadySelected = selectedFolders.value.some( const isAlreadySelected = selectedFolders.value.some(
(f) => f.id === folderToChoose.id (f) => f.id === folderToChoose.id,
); );
if (props.multiple) { if (props.multiple) {
if (isAlreadySelected) { if (isAlreadySelected) {
selectedFolders.value = selectedFolders.value.filter( selectedFolders.value = selectedFolders.value.filter(
(f) => f.id !== folderToChoose.id (f) => f.id !== folderToChoose.id,
); );
} else { } else {
selectedFolders.value.push(folderToChoose); selectedFolders.value.push(folderToChoose);
@ -707,7 +707,7 @@ function getMenu() {
icon: "ph-trash ph-bold ph-lg", icon: "ph-trash ph-bold ph-lg",
action: () => { action: () => {
deleteFolder( deleteFolder(
folder.value as Misskey.entities.DriveFolder folder.value as Misskey.entities.DriveFolder,
); );
}, },
} }
@ -725,7 +725,7 @@ function getMenu() {
function showMenu(ev: MouseEvent) { function showMenu(ev: MouseEvent) {
os.popupMenu( os.popupMenu(
getMenu(), getMenu(),
(ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined,
); );
} }

View File

@ -17,8 +17,8 @@
? i18n.ts.selectFiles ? i18n.ts.selectFiles
: i18n.ts.selectFolders : i18n.ts.selectFolders
: type === "file" : type === "file"
? i18n.ts.selectFile ? i18n.ts.selectFile
: i18n.ts.selectFolder : i18n.ts.selectFolder
}} }}
<span <span
v-if="selected.length > 0" v-if="selected.length > 0"
@ -50,7 +50,7 @@ withDefaults(
}>(), }>(),
{ {
type: "file", type: "file",
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{

View File

@ -16,7 +16,7 @@
class="_button" class="_button"
@click.prevent=" @click.prevent="
applyUnicodeSkinTone( applyUnicodeSkinTone(
props.skinTones.indexOf(skinTone) + 1 props.skinTones.indexOf(skinTone) + 1,
) )
" "
> >
@ -99,7 +99,7 @@ watch(
() => props.emojis, () => props.emojis,
(newVal) => { (newVal) => {
localEmojis.value = newVal.map(resolveEmojis); localEmojis.value = newVal.map(resolveEmojis);
} },
); );
</script> </script>

View File

@ -198,7 +198,7 @@ const props = withDefaults(
}>(), }>(),
{ {
showPinned: true, showPinned: true,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -218,13 +218,13 @@ const {
} = defaultStore.reactiveState; } = defaultStore.reactiveState;
const size = computed(() => const size = computed(() =>
props.asReactionPicker ? reactionPickerSize.value : 1 props.asReactionPicker ? reactionPickerSize.value : 1,
); );
const width = computed(() => const width = computed(() =>
props.asReactionPicker ? reactionPickerWidth.value : 3 props.asReactionPicker ? reactionPickerWidth.value : 3,
); );
const height = computed(() => const height = computed(() =>
props.asReactionPicker ? reactionPickerHeight.value : 2 props.asReactionPicker ? reactionPickerHeight.value : 2,
); );
const customEmojiCategories = emojiCategories; const customEmojiCategories = emojiCategories;
const customEmojis = instance.emojis; const customEmojis = instance.emojis;
@ -240,7 +240,7 @@ const resolveEmojis = (e: string) =>
const resolvedPinned = computed(() => pinned.value.map(resolveEmojis)); const resolvedPinned = computed(() => pinned.value.map(resolveEmojis));
const resolvedRecent = computed(() => const resolvedRecent = computed(() =>
recentlyUsedEmojis.value.map(resolveEmojis) recentlyUsedEmojis.value.map(resolveEmojis),
); );
watch(q, () => { watch(q, () => {
@ -282,8 +282,8 @@ watch(q, () => {
(keyword) => (keyword) =>
emoji.name.includes(keyword) || emoji.name.includes(keyword) ||
emoji.aliases.some((alias) => emoji.aliases.some((alias) =>
alias.includes(keyword) alias.includes(keyword),
) ),
) )
) { ) {
matches.add(emoji); matches.add(emoji);
@ -354,8 +354,8 @@ watch(q, () => {
(keyword) => (keyword) =>
emoji.slug.includes(keyword) || emoji.slug.includes(keyword) ||
emoji.keywords?.some((alias) => emoji.keywords?.some((alias) =>
alias.includes(keyword) alias.includes(keyword),
) ),
) )
) { ) {
matches.add(emoji); matches.add(emoji);
@ -459,7 +459,7 @@ function done(query?: any): boolean | void {
return true; return true;
} }
const exactMatchUnicode = emojilist.find( const exactMatchUnicode = emojilist.find(
(emoji) => emoji.emoji === q2 || emoji.slug === q2 (emoji) => emoji.emoji === q2 || emoji.slug === q2,
); );
if (exactMatchUnicode) { if (exactMatchUnicode) {
chosen(magUnicodeEmoji(exactMatchUnicode)); chosen(magUnicodeEmoji(exactMatchUnicode));

View File

@ -48,7 +48,7 @@ withDefaults(
manualShowing: null, manualShowing: null,
showPinned: true, showPinned: true,
asReactionPicker: false, asReactionPicker: false,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{

View File

@ -11,7 +11,7 @@
:key="file.id" :key="file.id"
v-tooltip.mfm=" v-tooltip.mfm="
`${file.type}\n${bytes(file.size)}\n${new Date( `${file.type}\n${bytes(file.size)}\n${new Date(
file.createdAt file.createdAt,
).toLocaleString()}\nby ${ ).toLocaleString()}\nby ${
file.user ? '@' + Acct.toString(file.user) : 'system' file.user ? '@' + Acct.toString(file.user) : 'system'
}` }`

View File

@ -56,7 +56,7 @@ export default defineComponent({
this.persistKey && this.persistKey &&
localStorage.getItem(localStoragePrefix + this.persistKey) localStorage.getItem(localStoragePrefix + this.persistKey)
? localStorage.getItem( ? localStorage.getItem(
localStoragePrefix + this.persistKey localStoragePrefix + this.persistKey,
) === "t" ) === "t"
: this.expanded, : this.expanded,
}; };
@ -66,7 +66,7 @@ export default defineComponent({
if (this.persistKey) { if (this.persistKey) {
localStorage.setItem( localStorage.setItem(
localStoragePrefix + this.persistKey, localStoragePrefix + this.persistKey,
this.showBody ? "t" : "f" this.showBody ? "t" : "f",
); );
} }
}, },
@ -85,9 +85,9 @@ export default defineComponent({
const bg = tinycolor( const bg = tinycolor(
rawBg.startsWith("var(") rawBg.startsWith("var(")
? getComputedStyle(document.documentElement).getPropertyValue( ? getComputedStyle(document.documentElement).getPropertyValue(
rawBg.slice(4, -1) rawBg.slice(4, -1),
) )
: rawBg : rawBg,
); );
bg.setAlpha(0.85); bg.setAlpha(0.85);
this.bg = bg.toRgbString(); this.bg = bg.toRgbString();
@ -123,7 +123,9 @@ export default defineComponent({
.folder-toggle-enter-active, .folder-toggle-enter-active,
.folder-toggle-leave-active { .folder-toggle-leave-active {
overflow-y: hidden; overflow-y: hidden;
transition: opacity 0.5s, height 0.5s !important; transition:
opacity 0.5s,
height 0.5s !important;
} }
.folder-toggle-enter-from { .folder-toggle-enter-from {
opacity: 0; opacity: 0;

View File

@ -28,7 +28,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeUnmount, onMounted } from "vue"; import { onBeforeUnmount, onMounted, ref } from "vue";
import type * as Misskey from "calckey-js"; import type * as Misskey from "calckey-js";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -44,20 +44,20 @@ const props = defineProps<{
user: packed.PackUserBase | Misskey.entities.User; user: packed.PackUserBase | Misskey.entities.User;
}>(); }>();
let state = $ref(i18n.ts.processing); let state = ref(i18n.ts.processing);
const connection = stream.useChannel("main"); const connection = stream.useChannel("main");
let waitIncoming = $ref(false); let waitIncoming = ref(false);
function accept(user: packed.PackUserBase | Misskey.entities.User) { function accept(user: packed.PackUserBase | Misskey.entities.User) {
waitIncoming = true; waitIncoming.value = true;
os.api("following/requests/accept", { userId: user.id }).then(() => { os.api("following/requests/accept", { userId: user.id }).then(() => {
globalEvents.emit("followeeProcessed", user); globalEvents.emit("followeeProcessed", user);
}); });
} }
function reject(user: packed.PackUserBase | Misskey.entities.User) { function reject(user: packed.PackUserBase | Misskey.entities.User) {
waitIncoming = true; waitIncoming.value = true;
os.api("following/requests/reject", { userId: user.id }).then(() => { os.api("following/requests/reject", { userId: user.id }).then(() => {
globalEvents.emit("followeeProcessed", user); globalEvents.emit("followeeProcessed", user);
}); });

View File

@ -77,7 +77,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, onBeforeUnmount, onMounted } from "vue"; import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import type * as Misskey from "calckey-js"; import type * as Misskey from "calckey-js";
import * as os from "@/os"; import * as os from "@/os";
import { stream } from "@/stream"; import { stream } from "@/stream";
@ -101,26 +101,26 @@ const props = withDefaults(
{ {
full: false, full: false,
large: false, large: false,
} },
); );
const isBlocking = computed(() => const isBlocking = computed(() =>
magTransProperty(props.user, "you_block", "isBlocking") magTransProperty(props.user, "you_block", "isBlocking"),
); );
let state = $ref(i18n.ts.processing); let state = ref(i18n.ts.processing);
let isFollowing = $ref( let isFollowing = ref(
magTransProperty(props.user, "you_follow", "isFollowing") magTransProperty(props.user, "you_follow", "isFollowing"),
); );
let hasPendingFollowRequestFromYou = $ref( let hasPendingFollowRequestFromYou = ref(
magTransProperty( magTransProperty(
props.user, props.user,
"you_request_follow", "you_request_follow",
"hasPendingFollowRequestFromYou" "hasPendingFollowRequestFromYou",
) ),
); );
let wait = $ref(false); let wait = ref(false);
const connection = stream.useChannel("main"); const connection = stream.useChannel("main");
if ( if (
@ -137,13 +137,14 @@ function onFollowChange(user: Misskey.entities.User) {
return; return;
if (user.id === props.user.id) { if (user.id === props.user.id) {
isFollowing = user.isFollowing; isFollowing.value = user.isFollowing;
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; hasPendingFollowRequestFromYou.value =
user.hasPendingFollowRequestFromYou;
} }
} }
async function onClick() { async function onClick() {
wait = true; wait.value = true;
try { try {
if (isBlocking.value) { if (isBlocking.value) {
@ -162,7 +163,7 @@ async function onClick() {
}); });
} }
emit("refresh"); emit("refresh");
} else if (isFollowing) { } else if (isFollowing.value) {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
type: "warning", type: "warning",
text: i18n.t("unfollowConfirm", { text: i18n.t("unfollowConfirm", {
@ -176,29 +177,29 @@ async function onClick() {
userId: props.user.id, userId: props.user.id,
}); });
} else { } else {
if (hasPendingFollowRequestFromYou) { if (hasPendingFollowRequestFromYou.value) {
await os.api("following/requests/cancel", { await os.api("following/requests/cancel", {
userId: props.user.id, userId: props.user.id,
}); });
hasPendingFollowRequestFromYou = false; hasPendingFollowRequestFromYou.value = false;
} else { } else {
await os.api("following/create", { await os.api("following/create", {
userId: props.user.id, userId: props.user.id,
}); });
hasPendingFollowRequestFromYou = true; hasPendingFollowRequestFromYou.value = true;
} }
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} finally { } finally {
wait = false; wait.value = false;
} }
} }
function menu(ev) { function menu(ev) {
os.popupMenu( os.popupMenu(
getUserMenu(props.user, router), getUserMenu(props.user, router),
ev.currentTarget ?? ev.target ev.currentTarget ?? ev.target,
); );
} }

View File

@ -62,6 +62,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import {} from "vue"; import {} from "vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
@ -75,20 +77,20 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let dialog: InstanceType<typeof XModalWindow> = $ref(); let dialog: InstanceType<typeof XModalWindow> = ref();
let username = $ref(""); let username = ref("");
let email = $ref(""); let email = ref("");
let processing = $ref(false); let processing = ref(false);
async function onSubmit() { async function onSubmit() {
processing = true; processing.value = true;
await os.apiWithDialog("request-reset-password", { await os.apiWithDialog("request-reset-password", {
username, username: username.value,
email, email: email.value,
}); });
emit("done"); emit("done");
dialog.close(); dialog.value.close();
} }
</script> </script>

View File

@ -18,7 +18,7 @@
<div class="xkpnjxcv _formRoot"> <div class="xkpnjxcv _formRoot">
<template <template
v-for="item in Object.keys(form).filter( v-for="item in Object.keys(form).filter(
(item) => !form[item].hidden (item) => !form[item].hidden,
)" )"
> >
<FormInput <FormInput

View File

@ -9,7 +9,7 @@ import * as os from "@/os";
export default defineComponent({ export default defineComponent({
components: { components: {
XFormula: defineAsyncComponent( XFormula: defineAsyncComponent(
() => import("@/components/MkFormulaCore.vue") () => import("@/components/MkFormulaCore.vue"),
), ),
}, },
props: { props: {

View File

@ -15,6 +15,8 @@ import {
onBeforeUnmount, onBeforeUnmount,
nextTick, nextTick,
watch, watch,
shallowRef,
ref,
} from "vue"; } from "vue";
import { Chart } from "chart.js"; import { Chart } from "chart.js";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
@ -33,11 +35,11 @@ const props = defineProps<{
src: string; src: string;
}>(); }>();
const rootEl = $shallowRef<HTMLDivElement>(null); const rootEl = shallowRef<HTMLDivElement>(null);
const chartEl = $shallowRef<HTMLCanvasElement>(null); const chartEl = shallowRef<HTMLCanvasElement>(null);
const now = new Date(); const now = new Date();
let chartInstance: Chart = null; let chartInstance: Chart = null;
let fetching = $ref(true); let fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip({ const { handler: externalTooltipHandler } = useChartTooltip({
position: "middle", position: "middle",
@ -53,8 +55,8 @@ async function renderChart() {
chartInstance.destroy(); chartInstance.destroy();
} }
const wide = rootEl.offsetWidth > 700; const wide = rootEl.value.offsetWidth > 700;
const narrow = rootEl.offsetWidth < 400; const narrow = rootEl.value.offsetWidth < 400;
const weeks = wide ? 50 : narrow ? 10 : 25; const weeks = wide ? 50 : narrow ? 10 : 25;
const chartLimit = 7 * weeks; const chartLimit = 7 * weeks;
@ -123,7 +125,7 @@ async function renderChart() {
values = addArrays(raw.diffs.normal, raw.diffs.reply, raw.diffs.renote); values = addArrays(raw.diffs.normal, raw.diffs.reply, raw.diffs.renote);
} }
fetching = false; fetching.value = false;
await nextTick(); await nextTick();
@ -141,7 +143,7 @@ async function renderChart() {
const marginEachCell = 4; const marginEachCell = 4;
chartInstance = new Chart(chartEl, { chartInstance = new Chart(chartEl.value, {
type: "matrix", type: "matrix",
data: { data: {
datasets: [ datasets: [
@ -257,9 +259,9 @@ async function renderChart() {
watch( watch(
() => props.src, () => props.src,
() => { () => {
fetching = true; fetching.value = true;
renderChart(); renderChart();
} },
); );
onMounted(async () => { onMounted(async () => {

View File

@ -26,6 +26,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import {} from "vue"; import {} from "vue";
import type * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import bytes from "@/filters/bytes"; import bytes from "@/filters/bytes";
@ -36,14 +38,14 @@ const props = withDefaults(
defineProps<{ defineProps<{
image: misskey.entities.DriveFile; image: misskey.entities.DriveFile;
}>(), }>(),
{} {},
); );
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
const modal = $ref<InstanceType<typeof MkModal>>(); const modal = ref<InstanceType<typeof MkModal>>();
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -20,7 +20,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from "vue"; import { onMounted, ref } from "vue";
import { decode } from "blurhash"; import { decode } from "blurhash";
const props = withDefaults( const props = withDefaults(
@ -40,23 +40,23 @@ const props = withDefaults(
title: null, title: null,
size: 64, size: 64,
cover: true, cover: true,
} },
); );
const canvas = $ref<HTMLCanvasElement>(); const canvas = ref<HTMLCanvasElement>();
let loaded = $ref(false); let loaded = ref(false);
function draw() { function draw() {
if (props.hash == null) return; if (props.hash == null) return;
const pixels = decode(props.hash, props.size, props.size); const pixels = decode(props.hash, props.size, props.size);
const ctx = canvas.getContext("2d"); const ctx = canvas.value.getContext("2d");
const imageData = ctx!.createImageData(props.size, props.size); const imageData = ctx!.createImageData(props.size, props.size);
imageData.data.set(pixels); imageData.data.set(pixels);
ctx!.putImageData(imageData, 0, 0); ctx!.putImageData(imageData, 0, 0);
} }
function onLoad() { function onLoad() {
loaded = true; loaded.value = true;
} }
onMounted(() => { onMounted(() => {

View File

@ -24,6 +24,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import * as calckey from "calckey-js"; import * as calckey from "calckey-js";
import MkMiniChart from "@/components/MkMiniChart.vue"; import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os"; import * as os from "@/os";
@ -33,7 +35,7 @@ const props = defineProps<{
instance: calckey.entities.Instance; instance: calckey.entities.Instance;
}>(); }>();
let chartValues = $ref<number[] | null>(null); let chartValues = ref<number[] | null>(null);
os.apiGet("charts/instance", { os.apiGet("charts/instance", {
host: props.instance.host, host: props.instance.host,
@ -42,7 +44,7 @@ os.apiGet("charts/instance", {
}).then((res) => { }).then((res) => {
// //
res.requests.received.splice(0, 1); res.requests.received.splice(0, 1);
chartValues = res.requests.received; chartValues.value = res.requests.received;
}); });
function getInstanceIcon(instance): string { function getInstanceIcon(instance): string {

View File

@ -56,6 +56,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
import * as os from "@/os"; import * as os from "@/os";
@ -68,47 +70,47 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let hostname = $ref(""); let hostname = ref("");
let instances: Instance[] = $ref([]); let instances: Instance[] = ref([]);
let selected: Instance | null = $ref(null); let selected: Instance | null = ref(null);
let dialogEl = $ref<InstanceType<typeof XModalWindow>>(); let dialogEl = ref<InstanceType<typeof XModalWindow>>();
let searchOrderLatch = 0; let searchOrderLatch = 0;
const search = () => { const search = () => {
if (hostname === "") { if (hostname.value === "") {
instances = []; instances.value = [];
return; return;
} }
const searchId = ++searchOrderLatch; const searchId = ++searchOrderLatch;
os.api("federation/instances", { os.api("federation/instances", {
host: hostname, host: hostname.value,
limit: 10, limit: 10,
blocked: false, blocked: false,
suspended: false, suspended: false,
sort: "+pubSub", sort: "+pubSub",
}).then((_instances) => { }).then((_instances) => {
if (searchId !== searchOrderLatch) return; if (searchId !== searchOrderLatch) return;
instances = _instances.map( instances.value = _instances.map(
(x) => (x) =>
({ ({
id: x.id, id: x.id,
host: x.host, host: x.host,
iconUrl: x.iconUrl, iconUrl: x.iconUrl,
} as Instance) }) as Instance,
); );
}); });
}; };
const ok = () => { const ok = () => {
if (selected == null) return; if (selected.value == null) return;
emit("ok", selected); emit("ok", selected.value);
dialogEl?.close(); dialogEl.value?.close();
}; };
const cancel = () => { const cancel = () => {
emit("cancel"); emit("cancel");
dialogEl?.close(); dialogEl.value?.close();
}; };
</script> </script>

View File

@ -102,7 +102,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from "vue"; import { onMounted, ref, shallowRef } from "vue";
import { Chart } from "chart.js"; import { Chart } from "chart.js";
import MkSelect from "@/components/form/select.vue"; import MkSelect from "@/components/form/select.vue";
import MkChart from "@/components/MkChart.vue"; import MkChart from "@/components/MkChart.vue";
@ -116,11 +116,11 @@ import { initChart } from "@/scripts/init-chart";
initChart(); initChart();
const chartLimit = 500; const chartLimit = 500;
let chartSpan = $ref<"hour" | "day">("hour"); let chartSpan = ref<"hour" | "day">("hour");
let chartSrc = $ref("active-users"); let chartSrc = ref("active-users");
let heatmapSrc = $ref("active-users"); let heatmapSrc = ref("active-users");
let subDoughnutEl = $shallowRef<HTMLCanvasElement>(); let subDoughnutEl = shallowRef<HTMLCanvasElement>();
let pubDoughnutEl = $shallowRef<HTMLCanvasElement>(); let pubDoughnutEl = shallowRef<HTMLCanvasElement>();
const { handler: externalTooltipHandler1 } = useChartTooltip({ const { handler: externalTooltipHandler1 } = useChartTooltip({
position: "middle", position: "middle",
@ -138,7 +138,7 @@ function createDoughnut(chartEl, tooltip, data) {
{ {
backgroundColor: data.map((x) => x.color), backgroundColor: data.map((x) => x.color),
borderColor: getComputedStyle( borderColor: getComputedStyle(
document.documentElement document.documentElement,
).getPropertyValue("--panel"), ).getPropertyValue("--panel"),
borderWidth: 2, borderWidth: 2,
hoverOffset: 0, hoverOffset: 0,
@ -161,7 +161,7 @@ function createDoughnut(chartEl, tooltip, data) {
ev, ev,
"nearest", "nearest",
{ intersect: true }, { intersect: true },
false false,
)[0]; )[0];
if (hit && data[hit.index].onClick) { if (hit && data[hit.index].onClick) {
data[hit.index].onClick(); data[hit.index].onClick();
@ -189,7 +189,7 @@ function createDoughnut(chartEl, tooltip, data) {
onMounted(() => { onMounted(() => {
os.apiGet("federation/stats", { limit: 30 }).then((fedStats) => { os.apiGet("federation/stats", { limit: 30 }).then((fedStats) => {
createDoughnut( createDoughnut(
subDoughnutEl, subDoughnutEl.value,
externalTooltipHandler1, externalTooltipHandler1,
fedStats.topSubInstances fedStats.topSubInstances
.map((x) => ({ .map((x) => ({
@ -206,11 +206,11 @@ onMounted(() => {
color: "#80808080", color: "#80808080",
value: fedStats.otherFollowersCount, value: fedStats.otherFollowersCount,
}, },
]) ]),
); );
createDoughnut( createDoughnut(
pubDoughnutEl, pubDoughnutEl.value,
externalTooltipHandler2, externalTooltipHandler2,
fedStats.topPubInstances fedStats.topPubInstances
.map((x) => ({ .map((x) => ({
@ -227,7 +227,7 @@ onMounted(() => {
color: "#80808080", color: "#80808080",
value: fedStats.otherFollowingCount, value: fedStats.otherFollowingCount,
}, },
]) ]),
); );
}); });
}); });

View File

@ -4,7 +4,7 @@
v-tooltip=" v-tooltip="
capitalize( capitalize(
magTransProperty(instance, 'software_name', 'softwareName') ?? magTransProperty(instance, 'software_name', 'softwareName') ??
'?' '?',
) )
" "
ref="ticker" ref="ticker"
@ -16,6 +16,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import { instanceName } from "@/config"; import { instanceName } from "@/config";
import { instance as Instance } from "@/instance"; import { instance as Instance } from "@/instance";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy"; import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
@ -27,7 +29,7 @@ const props = defineProps<{
instance?: Misskey.entities.User["instance"] | types.InstanceTicker | null; instance?: Misskey.entities.User["instance"] | types.InstanceTicker | null;
}>(); }>();
let ticker = $ref<HTMLElement | null>(null); let ticker = ref<HTMLElement | null>(null);
// if no instance data is given, this is for the local instance // if no instance data is given, this is for the local instance
const instance = props.instance ?? { const instance = props.instance ?? {
@ -38,7 +40,7 @@ const instance = props.instance ?? {
name: instanceName, name: instanceName,
themeColor: ( themeColor: (
document.querySelector( document.querySelector(
'meta[name="theme-color-orig"]' 'meta[name="theme-color-orig"]',
) as HTMLMetaElement ) as HTMLMetaElement
)?.content, )?.content,
softwareName: (Instance.softwareName || "Magnetar") as string | null, softwareName: (Instance.softwareName || "Magnetar") as string | null,
@ -59,18 +61,18 @@ const bg = {
}; };
function getInstanceIcon( function getInstanceIcon(
instance?: Misskey.entities.User["instance"] | types.InstanceTicker | null instance?: Misskey.entities.User["instance"] | types.InstanceTicker | null,
): string { ): string {
if (!instance) return "/client-assets/dummy.png"; if (!instance) return "/client-assets/dummy.png";
return ( return (
getProxiedImageUrlNullable( getProxiedImageUrlNullable(
magTransProperty(instance, "icon_url", "iconUrl"), magTransProperty(instance, "icon_url", "iconUrl"),
"preview" "preview",
) ?? ) ??
getProxiedImageUrlNullable( getProxiedImageUrlNullable(
magTransProperty(instance, "favicon_url", "faviconUrl"), magTransProperty(instance, "favicon_url", "faviconUrl"),
"preview" "preview",
) ?? ) ??
"/client-assets/dummy.png" "/client-assets/dummy.png"
); );
@ -106,8 +108,11 @@ function getInstanceIcon(
font-weight: bold; font-weight: bold;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
text-shadow: -1px -1px 0 var(--bg), 1px -1px 0 var(--bg), text-shadow:
-1px 1px 0 var(--bg), 1px 1px 0 var(--bg); -1px -1px 0 var(--bg),
1px -1px 0 var(--bg),
-1px 1px 0 var(--bg),
1px 1px 0 var(--bg);
.article > .main &, .article > .main &,
.header > .body & { .header > .body & {
display: unset; display: unset;

View File

@ -32,7 +32,7 @@ const props = withDefaults(
{ {
copy: null, copy: null,
oneline: false, oneline: false,
} },
); );
const copy_ = () => { const copy_ = () => {

View File

@ -46,6 +46,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import { navbarItemDef } from "@/navbar"; import { navbarItemDef } from "@/navbar";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -59,7 +61,7 @@ const props = withDefaults(
}>(), }>(),
{ {
anchor: () => ({ x: "right", y: "center" }), anchor: () => ({ x: "right", y: "center" }),
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -70,10 +72,10 @@ const preferedModalType =
deviceKind === "desktop" && props.src != null deviceKind === "desktop" && props.src != null
? "popup" ? "popup"
: deviceKind === "smartphone" : deviceKind === "smartphone"
? "drawer" ? "drawer"
: "dialog"; : "dialog";
const modal = $ref<InstanceType<typeof MkModal>>(); const modal = ref<InstanceType<typeof MkModal>>();
const menu = defaultStore.state.menu; const menu = defaultStore.state.menu;
@ -91,7 +93,7 @@ const items = Object.keys(navbarItemDef)
})); }));
function close() { function close() {
modal.close(); modal.value.close();
} }
</script> </script>

View File

@ -18,7 +18,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent } from "vue"; import { defineAsyncComponent, ref } from "vue";
import { url as local } from "@/config"; import { url as local } from "@/config";
import { useTooltip } from "@/scripts/use-tooltip"; import { useTooltip } from "@/scripts/use-tooltip";
import * as os from "@/os"; import * as os from "@/os";
@ -28,27 +28,27 @@ const props = withDefaults(
url: string; url: string;
rel?: null | string; rel?: null | string;
}>(), }>(),
{} {},
); );
const self = props.url.startsWith(local); const self = props.url.startsWith(local);
const attr = self ? "to" : "href"; const attr = self ? "to" : "href";
const target = self ? null : "_blank"; const target = self ? null : "_blank";
const el = $ref(); const el = ref();
useTooltip($$(el), (showing) => { useTooltip(el, (showing) => {
os.popup( os.popup(
defineAsyncComponent( defineAsyncComponent(
() => import("@/components/MkUrlPreviewPopup.vue") () => import("@/components/MkUrlPreviewPopup.vue"),
), ),
{ {
showing, showing,
url: props.url, url: props.url,
source: el, source: el.value,
}, },
{}, {},
"closed" "closed",
); );
}); });
</script> </script>

View File

@ -63,9 +63,9 @@ export default {
: undefined, : undefined,
}, },
}, },
$slots.default() $slots.default(),
) ),
) ),
), ),
]); ]);
}, },

View File

@ -17,30 +17,7 @@
" "
class="audio" class="audio"
> >
<VuePlyr <MagVideoPlayer :video="media"></MagVideoPlayer>
:options="{
controls: [
'play-large',
'play',
'progress',
'current-time',
'mute',
'volume',
'download',
],
disableContextMenu: false,
}"
>
<audio
ref="audioEl"
class="audio"
:src="media.url"
:title="media.name"
controls
preload="metadata"
@volumechange="volumechange"
/>
</VuePlyr>
</div> </div>
<a <a
v-else v-else
@ -58,11 +35,9 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from "vue"; import { onMounted, ref } from "vue";
import VuePlyr from "vue-plyr";
import type * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import { ColdDeviceStorage } from "@/store"; import { ColdDeviceStorage } from "@/store";
import "vue-plyr/dist/vue-plyr.css";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { packed } from "magnetar-common"; import { packed } from "magnetar-common";
import { magTransProperty } from "@/scripts-mag/mag-util"; import { magTransProperty } from "@/scripts-mag/mag-util";
@ -74,16 +49,8 @@ const props = withDefaults(
{} {}
); );
const audioEl = $ref<HTMLAudioElement | null>(); const audioEl = ref<HTMLAudioElement | null>();
let hide = $ref(true); let hide = ref(true);
function volumechange() {
if (audioEl) ColdDeviceStorage.set("mediaVolume", audioEl.volume);
}
onMounted(() => {
if (audioEl) audioEl.volume = ColdDeviceStorage.get("mediaVolume");
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -191,10 +191,10 @@ export default defineComponent({
caption() { caption() {
const img = document.getElementById( const img = document.getElementById(
"imgtocaption" "imgtocaption",
) as HTMLImageElement; ) as HTMLImageElement;
const ta = document.getElementById( const ta = document.getElementById(
"captioninput" "captioninput",
) as HTMLTextAreaElement; ) as HTMLTextAreaElement;
os.api("drive/files/caption-image", { os.api("drive/files/caption-image", {
url: img.src, url: img.src,

View File

@ -63,7 +63,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { watch } from "vue"; import { watch, ref } from "vue";
import type * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import * as os from "@/os"; import * as os from "@/os";
import { getStaticImageUrl } from "@/scripts/get-static-image-url"; import { getStaticImageUrl } from "@/scripts/get-static-image-url";
@ -78,7 +78,7 @@ const props = defineProps<{
raw?: boolean; raw?: boolean;
}>(); }>();
let hide = $ref(true); let hide = ref(true);
function noAltTextPopup(event: MouseEvent) { function noAltTextPopup(event: MouseEvent) {
os.alert({ os.alert({
@ -104,16 +104,16 @@ const url =
props.raw || defaultStore.state.loadRawImages props.raw || defaultStore.state.loadRawImages
? props.image.url ? props.image.url
: defaultStore.state.disableShowingAnimatedImages : defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl( ? getStaticImageUrl(
magTransProperty(props.image, "thumbnail_url", "thumbnailUrl")! magTransProperty(props.image, "thumbnail_url", "thumbnailUrl")!,
) )
: magTransProperty(props.image, "thumbnail_url", "thumbnailUrl")!; : magTransProperty(props.image, "thumbnail_url", "thumbnailUrl")!;
// Plugin:register_note_view_interruptor 使watch // Plugin:register_note_view_interruptor 使watch
watch( watch(
() => props.image, () => props.image,
() => { () => {
hide = hide.value =
defaultStore.state.nsfw === "force" defaultStore.state.nsfw === "force"
? true ? true
: magTransProperty(props.image, "sensitive", "isSensitive") && : magTransProperty(props.image, "sensitive", "isSensitive") &&
@ -122,7 +122,7 @@ watch(
{ {
deep: true, deep: true,
immediate: true, immediate: true,
} },
); );
</script> </script>

View File

@ -14,7 +14,7 @@
<div ref="gallery" @click.stop> <div ref="gallery" @click.stop>
<template <template
v-for="media in mediaList.filter((media) => v-for="media in mediaList.filter((media) =>
previewable(media) previewable(media),
)" )"
> >
<XVideo <XVideo
@ -22,7 +22,7 @@
magTransProperty( magTransProperty(
media, media,
'mime_type', 'mime_type',
'type' 'type',
).startsWith('video') ).startsWith('video')
" "
:key="media.id" :key="media.id"
@ -33,7 +33,7 @@
magTransProperty( magTransProperty(
media, media,
'mime_type', 'mime_type',
'type' 'type',
).startsWith('image') ).startsWith('image')
" "
:key="media.id" :key="media.id"
@ -87,7 +87,7 @@ onMounted(() => {
const properties = magTransProperty( const properties = magTransProperty(
media, media,
"media_metadata", "media_metadata",
"properties" "properties",
); );
const item = { const item = {
@ -142,7 +142,7 @@ onMounted(() => {
const properties = magTransProperty( const properties = magTransProperty(
file, file,
"media_metadata", "media_metadata",
"properties" "properties",
); );
itemData.src = file.url; itemData.src = file.url;
@ -205,7 +205,7 @@ onMounted(() => {
}); });
const previewable = ( const previewable = (
file: packed.PackDriveFileBase | misskey.entities.DriveFile file: packed.PackDriveFileBase | misskey.entities.DriveFile,
): boolean => { ): boolean => {
const type = magTransProperty(file, "mime_type", "type"); const type = magTransProperty(file, "mime_type", "type");
@ -217,7 +217,7 @@ const previewable = (
); );
}; };
const previewableCount = props.mediaList.filter((media) => const previewableCount = props.mediaList.filter((media) =>
previewable(media) previewable(media),
).length; ).length;
</script> </script>

View File

@ -12,40 +12,8 @@
<span>{{ i18n.ts.clickToShow }}</span> <span>{{ i18n.ts.clickToShow }}</span>
</div> </div>
</div> </div>
<div v-else class="video" :class="{ mini }"> <div v-else class="video">
<VuePlyr <MagVideoPlayer :video="video"> </MagVideoPlayer>
ref="plyr"
:options="{
controls: [
'play-large',
'play',
'progress',
'current-time',
'mute',
'volume',
'pip',
'download',
'fullscreen',
],
disableContextMenu: false,
}"
>
<video
:poster="
magTransProperty(video, 'thumbnail_url', 'thumbnailUrl')
"
:title="video.comment"
:aria-label="video.comment"
preload="none"
controls
@contextmenu.stop
>
<source
:src="video.url"
:type="magTransProperty(video, 'mime_type', 'type')"
/>
</video>
</VuePlyr>
<button <button
v-tooltip="i18n.ts.hide" v-tooltip="i18n.ts.hide"
class="_button hide" class="_button hide"
@ -57,43 +25,29 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from "vue"; import { ref } from "vue";
import VuePlyr from "vue-plyr";
import type * as misskey from "calckey-js"; import type * as misskey from "calckey-js";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import "vue-plyr/dist/vue-plyr.css";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { packed } from "magnetar-common"; import { packed } from "magnetar-common";
import { magTransProperty } from "@/scripts-mag/mag-util"; import { magTransProperty } from "@/scripts-mag/mag-util";
import MagVideoPlayer from "./MagVideoPlayer.vue";
const props = defineProps<{ const props = defineProps<{
video: packed.PackDriveFileBase | misskey.entities.DriveFile; video: packed.PackDriveFileBase | misskey.entities.DriveFile;
}>(); }>();
const plyr = ref();
const mini = ref(false);
const hide = ref( const hide = ref(
defaultStore.state.nsfw === "force" defaultStore.state.nsfw === "force"
? true ? true
: magTransProperty(props.video, "sensitive", "isSensitive") && : magTransProperty(props.video, "sensitive", "isSensitive") &&
defaultStore.state.nsfw !== "ignore" defaultStore.state.nsfw !== "ignore"
); );
onMounted(() => {
mini.value = plyr.value.player.media.scrollWidth < 300;
if (mini.value) {
plyr.value.player.on("play", () => {
plyr.value.player.fullscreen.enter();
});
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.video { .video {
position: relative; position: relative;
--plyr-color-main: var(--accent);
> .hide { > .hide {
display: block; display: block;
@ -113,31 +67,6 @@ onMounted(() => {
display: block; display: block;
} }
} }
> video {
display: flex;
justify-content: center;
align-items: center;
font-size: 3.5em;
overflow: hidden;
background-position: center;
background-size: cover;
width: 100%;
height: 100%;
}
&.mini {
:deep(.plyr:not(:fullscreen)) {
min-width: unset !important;
.plyr__control--overlaid,
.plyr__progress__container,
.plyr__volume,
[data-plyr="fullscreen"] {
display: none;
}
}
}
} }
.icozogqfvdetwohsdglrbswgrejoxbdj { .icozogqfvdetwohsdglrbswgrejoxbdj {

View File

@ -213,19 +213,19 @@ const emit = defineEmits<{
(ev: "close", actioned?: boolean): void; (ev: "close", actioned?: boolean): void;
}>(); }>();
let itemsEl = $ref<HTMLDivElement>(); let itemsEl = ref<HTMLDivElement>();
let items2: InnerMenuItem[] = $ref([]); let items2: InnerMenuItem[] = ref([]);
let child = $ref<InstanceType<typeof XChild>>(); let child = ref<InstanceType<typeof XChild>>();
let childShowingItem = $ref<MenuItem | null>(); let childShowingItem = ref<MenuItem | null>();
watch( watch(
() => props.items, () => props.items,
() => { () => {
const items: (MenuItem | MenuPending)[] = [...props.items].filter( const items: (MenuItem | MenuPending)[] = [...props.items].filter(
(item) => item !== undefined (item) => item !== undefined,
); );
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
@ -235,24 +235,24 @@ watch(
// if item is Promise // if item is Promise
items[i] = { type: "pending" }; items[i] = { type: "pending" };
item.then((actualItem) => { item.then((actualItem) => {
items2[i] = actualItem; items2.value[i] = actualItem;
}); });
} }
} }
items2 = items as InnerMenuItem[]; items2.value = items as InnerMenuItem[];
}, },
{ {
immediate: true, immediate: true,
} },
); );
let childMenu = $ref<MenuItem[] | null>(); let childMenu = ref<MenuItem[] | null>();
let childTarget = $ref<HTMLElement | null>(); let childTarget = ref<HTMLElement | null>();
function closeChild() { function closeChild() {
childMenu = null; childMenu.value = null;
childShowingItem = null; childShowingItem.value = null;
} }
function childActioned() { function childActioned() {
@ -262,11 +262,12 @@ function childActioned() {
function onGlobalMousedown(event: MouseEvent) { function onGlobalMousedown(event: MouseEvent) {
if ( if (
childTarget && childTarget.value &&
(event.target === childTarget || childTarget.contains(event.target)) (event.target === childTarget.value ||
childTarget.value.contains(event.target))
) )
return; return;
if (child && child.checkHit(event)) return; if (child.value && child.value.checkHit(event)) return;
closeChild(); closeChild();
} }
@ -285,9 +286,9 @@ async function showChildren(item: MenuItem, ev: MouseEvent) {
os.popupMenu(item.children, ev.currentTarget ?? ev.target); os.popupMenu(item.children, ev.currentTarget ?? ev.target);
close(); close();
} else { } else {
childTarget = ev.currentTarget ?? ev.target; childTarget.value = ev.currentTarget ?? ev.target;
childMenu = item.children; childMenu.value = item.children;
childShowingItem = item; childShowingItem.value = item;
} }
} }

View File

@ -25,7 +25,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onUnmounted, watch } from "vue"; import { onUnmounted, watch, ref } from "vue";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import tinycolor from "tinycolor2"; import tinycolor from "tinycolor2";
import { useInterval } from "@/scripts/use-interval"; import { useInterval } from "@/scripts/use-interval";
@ -37,13 +37,13 @@ const props = defineProps<{
const viewBoxX = 50; const viewBoxX = 50;
const viewBoxY = 50; const viewBoxY = 50;
const gradientId = uuid(); const gradientId = uuid();
let polylinePoints = $ref(""); let polylinePoints = ref("");
let polygonPoints = $ref(""); let polygonPoints = ref("");
let headX = $ref<number | null>(null); let headX = ref<number | null>(null);
let headY = $ref<number | null>(null); let headY = ref<number | null>(null);
let clock = $ref<number | null>(null); let clock = ref<number | null>(null);
const accent = tinycolor( const accent = tinycolor(
getComputedStyle(document.documentElement).getPropertyValue("--accent") getComputedStyle(document.documentElement).getPropertyValue("--accent"),
); );
const color = accent.toRgbString(); const color = accent.toRgbString();
@ -56,12 +56,14 @@ function draw(): void {
(1 - n / peak) * viewBoxY, (1 - n / peak) * viewBoxY,
]); ]);
polylinePoints = _polylinePoints.map((xy) => `${xy[0]},${xy[1]}`).join(" "); polylinePoints.value = _polylinePoints
.map((xy) => `${xy[0]},${xy[1]}`)
.join(" ");
polygonPoints = `0,${viewBoxY} ${polylinePoints} ${viewBoxX},${viewBoxY}`; polygonPoints.value = `0,${viewBoxY} ${polylinePoints.value} ${viewBoxX},${viewBoxY}`;
headX = _polylinePoints[_polylinePoints.length - 1][0]; headX.value = _polylinePoints[_polylinePoints.length - 1][0];
headY = _polylinePoints[_polylinePoints.length - 1][1]; headY.value = _polylinePoints[_polylinePoints.length - 1][1];
} }
watch(() => props.src, draw, { immediate: true }); watch(() => props.src, draw, { immediate: true });

View File

@ -77,7 +77,16 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, provide, watch } from "vue"; import {
nextTick,
onMounted,
onUnmounted,
provide,
watch,
ref,
shallowRef,
computed,
} from "vue";
import * as os from "@/os"; import * as os from "@/os";
import { isTouchUsing } from "@/scripts/touch"; import { isTouchUsing } from "@/scripts/touch";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
@ -116,7 +125,7 @@ const props = withDefaults(
noOverlap: true, noOverlap: true,
transparentBg: false, transparentBg: false,
noReturnFocus: false, noReturnFocus: false,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -130,14 +139,14 @@ const emit = defineEmits<{
provide("modal", true); provide("modal", true);
let maxHeight = $ref<number>(); let maxHeight = ref<number>();
let fixed = $ref(false); let fixed = ref(false);
let transformOrigin = $ref("center"); let transformOrigin = ref("center");
let showing = $ref(true); let showing = ref(true);
let content = $shallowRef<HTMLElement>(); let content = shallowRef<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority); const zIndex = os.claimZIndex(props.zPriority);
let useSendAnime = $ref(false); let useSendAnime = ref(false);
const type = $computed<ModalTypes>(() => { const type = computed<ModalTypes>(() => {
if (props.preferType === "auto") { if (props.preferType === "auto") {
if ( if (
!defaultStore.state.disableDrawer && !defaultStore.state.disableDrawer &&
@ -152,30 +161,30 @@ const type = $computed<ModalTypes>(() => {
return props.preferType!; return props.preferType!;
} }
}); });
const isEnableBgTransparent = $computed( const isEnableBgTransparent = computed(
() => props.transparentBg && type === "popup" () => props.transparentBg && type.value === "popup",
); );
let transitionName = $computed(() => let transitionName = computed(() =>
defaultStore.state.animation defaultStore.state.animation
? useSendAnime ? useSendAnime.value
? "send" ? "send"
: type === "drawer" : type.value === "drawer"
? "modal-drawer" ? "modal-drawer"
: type === "popup" : type.value === "popup"
? "modal-popup" ? "modal-popup"
: "modal" : "modal"
: "" : "",
); );
let transitionDuration = $computed(() => let transitionDuration = computed(() =>
transitionName === "send" transitionName.value === "send"
? 400 ? 400
: transitionName === "modal-popup" : transitionName.value === "modal-popup"
? 100 ? 100
: transitionName === "modal" : transitionName.value === "modal"
? 200 ? 200
: transitionName === "modal-drawer" : transitionName.value === "modal-drawer"
? 200 ? 200
: 0 : 0,
); );
let contentClicking = false; let contentClicking = false;
@ -187,12 +196,12 @@ function close(ev, opts: { useSendAnimation?: boolean } = {}) {
// history.forward(); // history.forward();
// } // }
if (opts.useSendAnimation) { if (opts.useSendAnimation) {
useSendAnime = true; useSendAnime.value = true;
} }
// eslint-disable-next-line vue/no-mutating-props // eslint-disable-next-line vue/no-mutating-props
if (props.src) props.src.style.pointerEvents = "auto"; if (props.src) props.src.style.pointerEvents = "auto";
showing = false; showing.value = false;
emit("close"); emit("close");
if (!props.noReturnFocus) { if (!props.noReturnFocus) {
focusedElement.focus(); focusedElement.focus();
@ -204,8 +213,8 @@ function onBgClick() {
emit("click"); emit("click");
} }
if (type === "drawer") { if (type.value === "drawer") {
maxHeight = window.innerHeight / 1.5; maxHeight.value = window.innerHeight / 1.5;
} }
const keymap = { const keymap = {
@ -216,21 +225,21 @@ const MARGIN = 16;
const align = () => { const align = () => {
if (props.src == null) return; if (props.src == null) return;
if (type === "drawer") return; if (type.value === "drawer") return;
if (type === "dialog") return; if (type.value === "dialog") return;
if (content == null) return; if (content.value == null) return;
const srcRect = props.src.getBoundingClientRect(); const srcRect = props.src.getBoundingClientRect();
const width = content!.offsetWidth; const width = content.value!.offsetWidth;
const height = content!.offsetHeight; const height = content.value!.offsetHeight;
let left; let left;
let top; let top;
const x = srcRect.left + (fixed ? 0 : window.pageXOffset); const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed ? 0 : window.pageYOffset); const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
if (props.anchor.x === "center") { if (props.anchor.x === "center") {
left = x + props.src.offsetWidth / 2 - width / 2; left = x + props.src.offsetWidth / 2 - width / 2;
@ -248,7 +257,7 @@ const align = () => {
top = y + props.src.offsetHeight; top = y + props.src.offsetHeight;
} }
if (fixed) { if (fixed.value) {
// //
if (left + width > window.innerWidth) { if (left + width > window.innerWidth) {
left = window.innerWidth - width; left = window.innerWidth - width;
@ -261,16 +270,16 @@ const align = () => {
if (top + height > window.innerHeight - MARGIN) { if (top + height > window.innerHeight - MARGIN) {
if (props.noOverlap && props.anchor.x === "center") { if (props.noOverlap && props.anchor.x === "center") {
if (underSpace >= upperSpace / 3) { if (underSpace >= upperSpace / 3) {
maxHeight = underSpace; maxHeight.value = underSpace;
} else { } else {
maxHeight = upperSpace; maxHeight.value = upperSpace;
top = upperSpace + MARGIN - height; top = upperSpace + MARGIN - height;
} }
} else { } else {
top = window.innerHeight - MARGIN - height; top = window.innerHeight - MARGIN - height;
} }
} else { } else {
maxHeight = underSpace; maxHeight.value = underSpace;
} }
} else { } else {
// //
@ -286,9 +295,9 @@ const align = () => {
if (top + height - window.scrollY > window.innerHeight - MARGIN) { if (top + height - window.scrollY > window.innerHeight - MARGIN) {
if (props.noOverlap && props.anchor.x === "center") { if (props.noOverlap && props.anchor.x === "center") {
if (underSpace >= upperSpace / 3) { if (underSpace >= upperSpace / 3) {
maxHeight = underSpace; maxHeight.value = underSpace;
} else { } else {
maxHeight = upperSpace; maxHeight.value = upperSpace;
top = window.scrollY + (upperSpace + MARGIN - height); top = window.scrollY + (upperSpace + MARGIN - height);
} }
} else { } else {
@ -300,7 +309,7 @@ const align = () => {
1; 1;
} }
} else { } else {
maxHeight = underSpace; maxHeight.value = underSpace;
} }
} }
@ -317,36 +326,43 @@ const align = () => {
if ( if (
top >= top >=
srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset) srcRect.top +
props.src.offsetHeight +
(fixed.value ? 0 : window.pageYOffset)
) { ) {
transformOriginY = "top"; transformOriginY = "top";
} else if (top + height <= srcRect.top + (fixed ? 0 : window.pageYOffset)) { } else if (
top + height <=
srcRect.top + (fixed.value ? 0 : window.pageYOffset)
) {
transformOriginY = "bottom"; transformOriginY = "bottom";
} }
if ( if (
left >= left >=
srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset) srcRect.left +
props.src.offsetWidth +
(fixed.value ? 0 : window.pageXOffset)
) { ) {
transformOriginX = "left"; transformOriginX = "left";
} else if ( } else if (
left + width <= left + width <=
srcRect.left + (fixed ? 0 : window.pageXOffset) srcRect.left + (fixed.value ? 0 : window.pageXOffset)
) { ) {
transformOriginX = "right"; transformOriginX = "right";
} }
transformOrigin = `${transformOriginX} ${transformOriginY}`; transformOrigin.value = `${transformOriginX} ${transformOriginY}`;
content.style.left = left + "px"; content.value.style.left = left + "px";
content.style.top = top + "px"; content.value.style.top = top + "px";
}; };
const onOpened = () => { const onOpened = () => {
emit("opened"); emit("opened");
// //
const el = content!.children[0]; const el = content.value!.children[0];
el.addEventListener( el.addEventListener(
"mousedown", "mousedown",
(ev) => { (ev) => {
@ -359,10 +375,10 @@ const onOpened = () => {
contentClicking = false; contentClicking = false;
}, 100); }, 100);
}, },
{ passive: true, once: true } { passive: true, once: true },
); );
}, },
{ passive: true } { passive: true },
); );
// if (props.preferType == "dialog") { // if (props.preferType == "dialog") {
// history.pushState(null, "", location.href); // history.pushState(null, "", location.href);
@ -378,19 +394,20 @@ onMounted(() => {
// eslint-disable-next-line vue/no-mutating-props // eslint-disable-next-line vue/no-mutating-props
props.src.style.pointerEvents = "none"; props.src.style.pointerEvents = "none";
} }
fixed = type === "drawer" || getFixedContainer(props.src) != null; fixed.value =
type.value === "drawer" || getFixedContainer(props.src) != null;
await nextTick(); await nextTick();
align(); align();
}, },
{ immediate: true } { immediate: true },
); );
nextTick(() => { nextTick(() => {
new ResizeObserver((entries, observer) => { new ResizeObserver((entries, observer) => {
align(); align();
}).observe(content!); }).observe(content.value!);
}); });
}); });
onUnmounted(() => { onUnmounted(() => {
@ -414,7 +431,8 @@ defineExpose({
> .content { > .content {
transform: translateY(0px); transform: translateY(0px);
transition: opacity 0.3s ease-in, transition:
opacity 0.3s ease-in,
transform 0.3s cubic-bezier(0.5, -0.5, 1, 0.5) !important; transform 0.3s cubic-bezier(0.5, -0.5, 1, 0.5) !important;
} }
} }
@ -439,7 +457,9 @@ defineExpose({
> .content { > .content {
transform-origin: var(--transformOrigin); transform-origin: var(--transformOrigin);
transition: opacity 0.2s, transform 0.2s !important; transition:
opacity 0.2s,
transform 0.2s !important;
} }
} }
.transition_modal_enterFrom, .transition_modal_enterFrom,
@ -464,7 +484,8 @@ defineExpose({
> .content { > .content {
transform-origin: var(--transformOrigin); transform-origin: var(--transformOrigin);
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transition:
opacity 0.2s cubic-bezier(0, 0, 0.2, 1),
transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important; transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
} }
} }

View File

@ -48,7 +48,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ComputedRef, provide } from "vue"; import { ComputedRef, provide, ref, computed } from "vue";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import { popout as _popout } from "@/scripts/popout"; import { popout as _popout } from "@/scripts/popout";
import copyToClipboard from "@/scripts/copy-to-clipboard"; import copyToClipboard from "@/scripts/copy-to-clipboard";
@ -76,27 +76,27 @@ const router = new Router(routes, props.initialPath);
router.addListener("push", (ctx) => {}); router.addListener("push", (ctx) => {});
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let rootEl = $ref(); let rootEl = ref();
let modal = $ref<InstanceType<typeof MkModal>>(); let modal = ref<InstanceType<typeof MkModal>>();
let path = $ref(props.initialPath); let path = ref(props.initialPath);
let width = $ref(860); let width = ref(860);
let height = $ref(660); let height = ref(660);
const history = []; const history = [];
provide("router", router); provide("router", router);
provideMetadataReceiver((info) => { provideMetadataReceiver((info) => {
pageMetadata = info; pageMetadata.value = info;
}); });
provide("shouldOmitHeaderTitle", true); provide("shouldOmitHeaderTitle", true);
provide("shouldHeaderThin", true); provide("shouldHeaderThin", true);
const pageUrl = $computed(() => url + path); const pageUrl = computed(() => url + path.value);
const contextmenu = $computed(() => { const contextmenu = computed(() => {
return [ return [
{ {
type: "label", type: "label",
text: path, text: path.value,
}, },
{ {
icon: "ph-arrows-out-simple ph-bold ph-lg", icon: "ph-arrows-out-simple ph-bold ph-lg",
@ -113,15 +113,15 @@ const contextmenu = $computed(() => {
icon: "ph-arrow-square-out ph-bold ph-lg", icon: "ph-arrow-square-out ph-bold ph-lg",
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
action: () => { action: () => {
window.open(pageUrl, "_blank"); window.open(pageUrl.value, "_blank");
modal.close(); modal.value.close();
}, },
}, },
{ {
icon: "ph-link-simple ph-bold ph-lg", icon: "ph-link-simple ph-bold ph-lg",
text: i18n.ts.copyLink, text: i18n.ts.copyLink,
action: () => { action: () => {
copyToClipboard(pageUrl); copyToClipboard(pageUrl.value);
}, },
}, },
]; ];
@ -137,17 +137,17 @@ function back() {
} }
function expand() { function expand() {
mainRouter.push(path); mainRouter.push(path.value);
modal.close(); modal.value.close();
} }
function popout() { function popout() {
_popout(path, rootEl); _popout(path.value, rootEl.value);
modal.close(); modal.value.close();
} }
function onContextmenu(ev: MouseEvent) { function onContextmenu(ev: MouseEvent) {
os.contextMenu(contextmenu, ev); os.contextMenu(contextmenu.value, ev);
} }
</script> </script>

View File

@ -17,8 +17,8 @@
? `${props.height}px` ? `${props.height}px`
: null : null
: height : height
? `min(${props.height}px, 100%)` ? `min(${props.height}px, 100%)`
: '100%', : '100%',
}" }"
tabindex="-1" tabindex="-1"
> >
@ -62,6 +62,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { shallowRef } from "vue";
import { FocusTrap } from "focus-trap-vue"; import { FocusTrap } from "focus-trap-vue";
import MkModal from "./MkModal.vue"; import MkModal from "./MkModal.vue";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
@ -80,7 +82,7 @@ const props = withDefaults(
width: 400, width: 400,
height: null, height: null,
scroll: true, scroll: true,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -90,12 +92,12 @@ const emit = defineEmits<{
(event: "ok"): void; (event: "ok"): void;
}>(); }>();
let modal = $shallowRef<InstanceType<typeof MkModal>>(); let modal = shallowRef<InstanceType<typeof MkModal>>();
let rootEl = $shallowRef<HTMLElement>(); let rootEl = shallowRef<HTMLElement>();
let headerEl = $shallowRef<HTMLElement>(); let headerEl = shallowRef<HTMLElement>();
const close = (ev) => { const close = (ev) => {
modal?.close(ev); modal.value?.close(ev);
}; };
const onBgClick = () => { const onBgClick = () => {

View File

@ -22,6 +22,6 @@ watch(
}, },
{ {
immediate: true, immediate: true,
} },
); );
</script> </script>

View File

@ -30,7 +30,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ComputedRef, inject, provide } from "vue"; import { ComputedRef, inject, provide, ref, computed } from "vue";
import RouterView from "@/components/global/RouterView.vue"; import RouterView from "@/components/global/RouterView.vue";
import XWindow from "@/components/MkWindow.vue"; import XWindow from "@/components/MkWindow.vue";
import { popout as _popout } from "@/scripts/popout"; import { popout as _popout } from "@/scripts/popout";
@ -56,18 +56,18 @@ defineEmits<{
const router = new Router(routes, props.initialPath); const router = new Router(routes, props.initialPath);
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>(); let pageMetadata = ref<null | ComputedRef<PageMetadata>>();
let windowEl = $ref<InstanceType<typeof XWindow>>(); let windowEl = ref<InstanceType<typeof XWindow>>();
const history = $ref<{ path: string; key: any }[]>([ const history = ref<{ path: string; key: any }[]>([
{ {
path: router.getCurrentPath(), path: router.getCurrentPath(),
key: router.getCurrentKey(), key: router.getCurrentKey(),
}, },
]); ]);
const buttonsLeft = $computed(() => { const buttonsLeft = computed(() => {
const buttons = []; const buttons = [];
if (history.length > 1) { if (history.value.length > 1) {
buttons.push({ buttons.push({
icon: "ph-caret-left ph-bold ph-lg", icon: "ph-caret-left ph-bold ph-lg",
onClick: back, onClick: back,
@ -76,7 +76,7 @@ const buttonsLeft = $computed(() => {
return buttons; return buttons;
}); });
const buttonsRight = $computed(() => { const buttonsRight = computed(() => {
const buttons = [ const buttons = [
{ {
icon: "ph-arrows-out-simple ph-bold ph-lg", icon: "ph-arrows-out-simple ph-bold ph-lg",
@ -89,18 +89,18 @@ const buttonsRight = $computed(() => {
}); });
router.addListener("push", (ctx) => { router.addListener("push", (ctx) => {
history.push({ path: ctx.path, key: ctx.key }); history.value.push({ path: ctx.path, key: ctx.key });
}); });
provide("router", router); provide("router", router);
provideMetadataReceiver((info) => { provideMetadataReceiver((info) => {
pageMetadata = info; pageMetadata.value = info;
}); });
provide("shouldOmitHeaderTitle", true); provide("shouldOmitHeaderTitle", true);
provide("shouldBackButton", false); provide("shouldBackButton", false);
provide("shouldHeaderThin", true); provide("shouldHeaderThin", true);
const contextmenu = $computed(() => [ const contextmenu = computed(() => [
{ {
icon: "ph-arrows-out-simple ph-bold ph-lg", icon: "ph-arrows-out-simple ph-bold ph-lg",
text: i18n.ts.showInPage, text: i18n.ts.showInPage,
@ -116,7 +116,7 @@ const contextmenu = $computed(() => [
text: i18n.ts.openInNewTab, text: i18n.ts.openInNewTab,
action: () => { action: () => {
window.open(url + router.getCurrentPath(), "_blank"); window.open(url + router.getCurrentPath(), "_blank");
windowEl.close(); windowEl.value.close();
}, },
}, },
{ {
@ -129,29 +129,29 @@ const contextmenu = $computed(() => [
]); ]);
function menu(ev) { function menu(ev) {
os.popupMenu(contextmenu, ev.currentTarget ?? ev.target); os.popupMenu(contextmenu.value, ev.currentTarget ?? ev.target);
} }
function back() { function back() {
history.pop(); history.value.pop();
router.replace( router.replace(
history[history.length - 1].path, history.value[history.value.length - 1].path,
history[history.length - 1].key history.value[history.value.length - 1].key,
); );
} }
function close() { function close() {
windowEl.close(); windowEl.value.close();
} }
function expand() { function expand() {
mainRouter.push(router.getCurrentPath(), "forcePage"); mainRouter.push(router.getCurrentPath(), "forcePage");
windowEl.close(); windowEl.value.close();
} }
function popout() { function popout() {
_popout(router.getCurrentPath(), windowEl.$el); _popout(router.getCurrentPath(), windowEl.value.$el);
windowEl.close(); windowEl.value.close();
} }
defineExpose({ defineExpose({

View File

@ -85,7 +85,7 @@ import { magTransProperty } from "@/scripts-mag/mag-util";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
export type Paging< export type Paging<
E extends keyof misskey.Endpoints = keyof misskey.Endpoints E extends keyof misskey.Endpoints = keyof misskey.Endpoints,
> = { > = {
endpoint: E; endpoint: E;
limit: number; limit: number;
@ -119,7 +119,7 @@ const props = withDefaults(
}>(), }>(),
{ {
displayLimit: 30, displayLimit: 30,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -166,7 +166,7 @@ const init = async (): Promise<void> => {
(err) => { (err) => {
error.value = true; error.value = true;
fetching.value = false; fetching.value = false;
} },
); );
}; };
@ -206,7 +206,7 @@ const refresh = async (): Promise<void> => {
(err) => { (err) => {
error.value = true; error.value = true;
fetching.value = false; fetching.value = false;
} },
); );
}; };
@ -241,8 +241,8 @@ const fetchMore = async (): Promise<void> => {
magTransProperty( magTransProperty(
lastItem, lastItem,
"createdAt", "createdAt",
"created_at" "created_at",
) ),
).getTime() ).getTime()
: undefined, : undefined,
untilId: lastItem?.id ?? undefined, untilId: lastItem?.id ?? undefined,
@ -259,7 +259,7 @@ const fetchMore = async (): Promise<void> => {
}, },
(err) => { (err) => {
moreFetching.value = false; moreFetching.value = false;
} },
); );
}; };
@ -311,7 +311,7 @@ const prepend = (item: Item): void => {
queue.value = []; queue.value = [];
items.value = [...queueRemoved, ...items.value].slice( items.value = [...queueRemoved, ...items.value].slice(
0, 0,
props.displayLimit props.displayLimit,
); );
}); });
} }
@ -355,7 +355,7 @@ watch(
if (a.length === 0 && b.length === 0) return; if (a.length === 0 && b.length === 0) return;
emit("queue", queue.value.length); emit("queue", queue.value.length);
}, },
{ deep: true } { deep: true },
); );
init(); init();

View File

@ -96,7 +96,7 @@ const emit = defineEmits<{
expiredAfter: number; expiredAfter: number;
choices: string[]; choices: string[];
multiple: boolean; multiple: boolean;
} },
): void; ): void;
}>(); }>();
@ -104,7 +104,7 @@ const choices = ref(props.modelValue.choices);
const multiple = ref(props.modelValue.multiple); const multiple = ref(props.modelValue.multiple);
const expiration = ref("infinite"); const expiration = ref("infinite");
const atDate = ref( const atDate = ref(
formatDateTimeString(addTime(new Date(), 1, "day"), "yyyy-MM-dd") formatDateTimeString(addTime(new Date(), 1, "day"), "yyyy-MM-dd"),
); );
const atTime = ref("00:00"); const atTime = ref("00:00");
const after = ref(0); const after = ref(0);
@ -166,8 +166,8 @@ function get() {
...(expiration.value === "at" ...(expiration.value === "at"
? { expiresAt: calcAt() } ? { expiresAt: calcAt() }
: expiration.value === "after" : expiration.value === "after"
? { expiredAfter: calcAfter() } ? { expiredAfter: calcAfter() }
: {}), : {}),
}; };
} }
@ -176,7 +176,7 @@ watch(
() => emit("update:modelValue", get()), () => emit("update:modelValue", get()),
{ {
deep: true, deep: true,
} },
); );
</script> </script>

View File

@ -24,6 +24,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import {} from "vue"; import {} from "vue";
import MkModal from "./MkModal.vue"; import MkModal from "./MkModal.vue";
import MkMenu from "./MkMenu.vue"; import MkMenu from "./MkMenu.vue";
@ -42,7 +44,7 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let modal = $ref<InstanceType<typeof MkModal>>(); let modal = ref<InstanceType<typeof MkModal>>();
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -89,8 +89,8 @@
reply reply
? 'ph-arrow-u-up-left ph-bold ph-lg' ? 'ph-arrow-u-up-left ph-bold ph-lg'
: renote : renote
? 'ph-quotes ph-bold ph-lg' ? 'ph-quotes ph-bold ph-lg'
: 'ph-paper-plane-tilt ph-bold ph-lg' : 'ph-paper-plane-tilt ph-bold ph-lg'
" "
></i> ></i>
</button> </button>
@ -235,7 +235,15 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent, inject, nextTick, onMounted, watch } from "vue"; import {
defineAsyncComponent,
inject,
nextTick,
onMounted,
watch,
ref,
computed,
} from "vue";
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import insertTextAtCursor from "insert-text-at-cursor"; import insertTextAtCursor from "insert-text-at-cursor";
@ -295,7 +303,7 @@ const props = withDefaults(
initialVisibleUsers: () => [], initialVisibleUsers: () => [],
autofocus: true, autofocus: true,
showMfmCheatSheet: true, showMfmCheatSheet: true,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -304,47 +312,47 @@ const emit = defineEmits<{
(ev: "esc"): void; (ev: "esc"): void;
}>(); }>();
const textareaEl = $ref<HTMLTextAreaElement | null>(null); const textareaEl = ref<HTMLTextAreaElement | null>(null);
const cwInputEl = $ref<HTMLInputElement | null>(null); const cwInputEl = ref<HTMLInputElement | null>(null);
const hashtagsInputEl = $ref<HTMLInputElement | null>(null); const hashtagsInputEl = ref<HTMLInputElement | null>(null);
const visibilityButton = $ref<HTMLElement | null>(null); const visibilityButton = ref<HTMLElement | null>(null);
let posting = $ref(false); let posting = ref(false);
let text = $ref(props.initialText ?? ""); let text = ref(props.initialText ?? "");
let files = $ref(props.initialFiles ?? []); let files = ref(props.initialFiles ?? []);
let poll = $ref<{ let poll = ref<{
choices: string[]; choices: string[];
multiple: boolean; multiple: boolean;
expiresAt: string | null; expiresAt: string | null;
expiredAfter: string | null; expiredAfter: string | null;
} | null>(null); } | null>(null);
let useCw = $ref(false); let useCw = ref(false);
let showPreview = $ref(false); let showPreview = ref(false);
let cw = $ref<string | null>(null); let cw = ref<string | null>(null);
let localOnly = $ref<boolean>( let localOnly = ref<boolean>(
props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility
? defaultStore.state.localOnly ? defaultStore.state.localOnly
: defaultStore.state.defaultNoteLocalOnly : defaultStore.state.defaultNoteLocalOnly,
); );
let visibility = $ref( let visibility = ref(
props.initialVisibility ?? props.initialVisibility ??
((defaultStore.state.rememberNoteVisibility ((defaultStore.state.rememberNoteVisibility
? defaultStore.state.visibility ? defaultStore.state.visibility
: defaultStore.state : defaultStore.state
.defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]) .defaultNoteVisibility) as (typeof misskey.noteVisibilities)[number]),
); );
let visibleUsers = $ref([]); let visibleUsers = ref([]);
if (props.initialVisibleUsers) { if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser); props.initialVisibleUsers.forEach(pushVisibleUser);
} }
let autocomplete = $ref(null); let autocomplete = ref(null);
let draghover = $ref(false); let draghover = ref(false);
let quoteId = $ref<string | null>(null); let quoteId = ref<string | null>(null);
let hasNotSpecifiedMentions = $ref(false); let hasNotSpecifiedMentions = ref(false);
let recentHashtags = $ref(JSON.parse(localStorage.getItem("hashtags") || "[]")); let recentHashtags = ref(JSON.parse(localStorage.getItem("hashtags") || "[]"));
let imeText = $ref(""); let imeText = ref("");
const draftKey = $computed((): string => { const draftKey = computed((): string => {
if (props.editId) { if (props.editId) {
return `edit:${props.editId}`; return `edit:${props.editId}`;
} }
@ -362,7 +370,7 @@ const draftKey = $computed((): string => {
return key; return key;
}); });
const placeholder = $computed((): string => { const placeholder = computed((): string => {
if (props.renote) { if (props.renote) {
return i18n.ts._postForm.quotePlaceholder; return i18n.ts._postForm.quotePlaceholder;
} else if (props.reply) { } else if (props.reply) {
@ -380,57 +388,60 @@ const placeholder = $computed((): string => {
} }
}); });
const submitText = $computed((): string => { const submitText = computed((): string => {
return props.editId return props.editId
? i18n.ts.edit ? i18n.ts.edit
: props.renote : props.renote
? i18n.ts.quote ? i18n.ts.quote
: props.reply : props.reply
? i18n.ts.reply ? i18n.ts.reply
: i18n.ts.note; : i18n.ts.note;
}); });
const textLength = $computed((): number => { const textLength = computed((): number => {
return length((preprocess(text) + imeText).trim()); return length((preprocess(text.value) + imeText.value).trim());
}); });
const maxTextLength = $computed((): number => { const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 1000; return instance ? instance.maxNoteTextLength : 1000;
}); });
const canPost = $computed((): boolean => { const canPost = computed((): boolean => {
return ( return (
!posting && !posting.value &&
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) && (1 <= textLength.value ||
textLength <= maxTextLength && 1 <= files.value.length ||
(!poll || poll.choices.length >= 2) !!poll.value ||
!!props.renote) &&
textLength.value <= maxTextLength.value &&
(!poll.value || poll.value.choices.length >= 2)
); );
}); });
const withHashtags = $computed( const withHashtags = computed(
defaultStore.makeGetterSetter("postFormWithHashtags") defaultStore.makeGetterSetter("postFormWithHashtags"),
); );
const hashtags = $computed(defaultStore.makeGetterSetter("postFormHashtags")); const hashtags = computed(defaultStore.makeGetterSetter("postFormHashtags"));
watch($$(text), () => { watch(text, () => {
checkMissingMention(); checkMissingMention();
}); });
watch( watch(
$$(visibleUsers), visibleUsers,
() => { () => {
checkMissingMention(); checkMissingMention();
}, },
{ {
deep: true, deep: true,
} },
); );
if (props.mention) { if (props.mention) {
text = props.mention.host text.value = props.mention.host
? `@${props.mention.username}@${toASCII(props.mention.host)}` ? `@${props.mention.username}@${toASCII(props.mention.host)}`
: `@${props.mention.username}`; : `@${props.mention.username}`;
text += " "; text.value += " ";
} }
if ( if (
@ -438,7 +449,7 @@ if (
(props.reply.user.username !== $i.username || (props.reply.user.username !== $i.username ||
(props.reply.user.host != null && props.reply.user.host !== host)) (props.reply.user.host != null && props.reply.user.host !== host))
) { ) {
text = `@${props.reply.user.username}${ text.value = `@${props.reply.user.username}${
props.reply.user.host != null props.reply.user.host != null
? "@" + toASCII(props.reply.user.host) ? "@" + toASCII(props.reply.user.host)
: "" : ""
@ -453,17 +464,17 @@ if (props.reply && props.reply.text != null) {
const mention = x.host const mention = x.host
? `@${x.username}@${toASCII(x.host)}` ? `@${x.username}@${toASCII(x.host)}`
: otherHost == null || otherHost === host : otherHost == null || otherHost === host
? `@${x.username}` ? `@${x.username}`
: `@${x.username}@${toASCII(otherHost)}`; : `@${x.username}@${toASCII(otherHost)}`;
// //
if ($i.username === x.username && (x.host == null || x.host === host)) if ($i.username === x.username && (x.host == null || x.host === host))
continue; continue;
// //
if (text.includes(`${mention} `)) continue; if (text.value.includes(`${mention} `)) continue;
text += `${mention} `; text.value += `${mention} `;
} }
} }
@ -471,30 +482,30 @@ if (props.reply && props.reply.text != null) {
if ( if (
props.reply && props.reply &&
["home", "followers", "specified"].includes( ["home", "followers", "specified"].includes(
magLegacyVisibility(props.reply.visibility) magLegacyVisibility(props.reply.visibility),
) )
) { ) {
if ( if (
magLegacyVisibility(props.reply.visibility) === "home" && magLegacyVisibility(props.reply.visibility) === "home" &&
visibility === "followers" visibility.value === "followers"
) { ) {
visibility = "followers"; visibility.value = "followers";
} else if ( } else if (
["home", "followers"].includes( ["home", "followers"].includes(
magLegacyVisibility(props.reply.visibility) magLegacyVisibility(props.reply.visibility),
) && ) &&
visibility === "specified" visibility.value === "specified"
) { ) {
visibility = "specified"; visibility.value = "specified";
} else { } else {
visibility = magLegacyVisibility(props.reply.visibility); visibility.value = magLegacyVisibility(props.reply.visibility);
} }
if (visibility === "specified") { if (visibility.value === "specified") {
const ids = props.reply.visible_user_ids; const ids = props.reply.visible_user_ids;
if (ids) { if (ids) {
os.api("users/show", { os.api("users/show", {
userIds: ids.filter( userIds: ids.filter(
(uid) => uid !== $i.id && uid !== props.reply!.user.id (uid) => uid !== $i.id && uid !== props.reply!.user.id,
), ),
}).then((users) => { }).then((users) => {
users.forEach(pushVisibleUser); users.forEach(pushVisibleUser);
@ -505,74 +516,74 @@ if (
os.api("users/show", { userId: props.reply.user.id }).then( os.api("users/show", { userId: props.reply.user.id }).then(
(user) => { (user) => {
pushVisibleUser(user); pushVisibleUser(user);
} },
); );
} }
} }
} }
if (props.specified) { if (props.specified) {
visibility = "specified"; visibility.value = "specified";
pushVisibleUser(props.specified); pushVisibleUser(props.specified);
} }
// keep cw when reply // keep cw when reply
if (defaultStore.state.keepCw && props.reply && props.reply.cw) { if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
useCw = true; useCw.value = true;
cw = props.reply.cw; cw.value = props.reply.cw;
} }
function watchForDraft() { function watchForDraft() {
watch($$(text), () => saveDraft()); watch(text, () => saveDraft());
watch($$(useCw), () => saveDraft()); watch(useCw, () => saveDraft());
watch($$(cw), () => saveDraft()); watch(cw, () => saveDraft());
watch($$(poll), () => saveDraft()); watch(poll, () => saveDraft());
watch($$(files), () => saveDraft(), { deep: true }); watch(files, () => saveDraft(), { deep: true });
watch($$(visibility), () => saveDraft()); watch(visibility, () => saveDraft());
watch($$(localOnly), () => saveDraft()); watch(localOnly, () => saveDraft());
} }
function checkMissingMention() { function checkMissingMention() {
if (visibility === "specified") { if (visibility.value === "specified") {
const ast = mfm.parse(text); const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) { for (const x of extractMentions(ast)) {
if ( if (
!visibleUsers.some( !visibleUsers.value.some(
(u) => u.username === x.username && u.host === x.host (u) => u.username === x.username && u.host === x.host,
) )
) { ) {
hasNotSpecifiedMentions = true; hasNotSpecifiedMentions.value = true;
return; return;
} }
} }
hasNotSpecifiedMentions = false; hasNotSpecifiedMentions.value = false;
} }
} }
function addMissingMention() { function addMissingMention() {
const ast = mfm.parse(text); const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) { for (const x of extractMentions(ast)) {
if ( if (
!visibleUsers.some( !visibleUsers.value.some(
(u) => u.username === x.username && u.host === x.host (u) => u.username === x.username && u.host === x.host,
) )
) { ) {
os.api("users/show", { username: x.username, host: x.host }).then( os.api("users/show", { username: x.username, host: x.host }).then(
(user) => { (user) => {
visibleUsers.push(user); visibleUsers.value.push(user);
} },
); );
} }
} }
} }
function togglePoll() { function togglePoll() {
if (poll) { if (poll.value) {
poll = null; poll.value = null;
} else { } else {
poll = { poll.value = {
choices: ["", ""], choices: ["", ""],
multiple: false, multiple: false,
expiresAt: null, expiresAt: null,
@ -582,15 +593,15 @@ function togglePoll() {
} }
function addTag(tag: string) { function addTag(tag: string) {
insertTextAtCursor(textareaEl, ` #${tag} `); insertTextAtCursor(textareaEl.value, ` #${tag} `);
} }
function focus() { function focus() {
if (textareaEl) { if (textareaEl.value) {
textareaEl.focus(); textareaEl.value.focus();
textareaEl.setSelectionRange( textareaEl.value.setSelectionRange(
textareaEl.value.length, textareaEl.value.value.length,
textareaEl.value.length textareaEl.value.value.length,
); );
} }
} }
@ -599,72 +610,72 @@ function chooseFileFrom(ev) {
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then( selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(
(files_) => { (files_) => {
for (const file of files_) { for (const file of files_) {
files.push(file); files.value.push(file);
} }
} },
); );
} }
function detachFile(id) { function detachFile(id) {
files = files.filter((x) => x.id !== id); files.value = files.value.filter((x) => x.id !== id);
} }
function updateFiles(_files) { function updateFiles(_files) {
files = _files; files.value = _files;
} }
function updateFileSensitive(file, sensitive) { function updateFileSensitive(file, sensitive) {
const idx = files.findIndex((x) => x.id === file.id); const idx = files.value.findIndex((x) => x.id === file.id);
const f = files[idx]; const f = files.value[idx];
if ("isSensitive" in f) f.isSensitive = sensitive; if ("isSensitive" in f) f.isSensitive = sensitive;
else f.sensitive = sensitive; else f.sensitive = sensitive;
} }
function updateFileName(file, name) { function updateFileName(file, name) {
files[files.findIndex((x) => x.id === file.id)].name = name; files.value[files.value.findIndex((x) => x.id === file.id)].name = name;
} }
function upload(file: File, name?: string) { function upload(file: File, name?: string) {
uploadFile(file, defaultStore.state.uploadFolder, name).then((res) => { uploadFile(file, defaultStore.state.uploadFolder, name).then((res) => {
files.push(res); files.value.push(res);
}); });
} }
function setVisibility() { function setVisibility() {
os.popup( os.popup(
defineAsyncComponent( defineAsyncComponent(
() => import("@/components/MkVisibilityPicker.vue") () => import("@/components/MkVisibilityPicker.vue"),
), ),
{ {
currentVisibility: visibility, currentVisibility: visibility.value,
currentLocalOnly: localOnly, currentLocalOnly: localOnly.value,
src: visibilityButton, src: visibilityButton.value,
}, },
{ {
changeVisibility: (v) => { changeVisibility: (v) => {
visibility = v; visibility.value = v;
if (defaultStore.state.rememberNoteVisibility) { if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set("visibility", visibility); defaultStore.set("visibility", visibility.value);
} }
}, },
changeLocalOnly: (v) => { changeLocalOnly: (v) => {
localOnly = v; localOnly.value = v;
if (defaultStore.state.rememberNoteVisibility) { if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set("localOnly", localOnly); defaultStore.set("localOnly", localOnly.value);
} }
}, },
}, },
"closed" "closed",
); );
} }
function pushVisibleUser(user) { function pushVisibleUser(user) {
if ( if (
!visibleUsers.some( !visibleUsers.value.some(
(u) => u.username === user.username && u.host === user.host (u) => u.username === user.username && u.host === user.host,
) )
) { ) {
visibleUsers.push(user); visibleUsers.value.push(user);
} }
} }
@ -675,37 +686,37 @@ function addVisibleUser() {
} }
function removeVisibleUser(user) { function removeVisibleUser(user) {
visibleUsers = erase(user, visibleUsers); visibleUsers.value = erase(user, visibleUsers.value);
} }
function clear() { function clear() {
text = ""; text.value = "";
files = []; files.value = [];
poll = null; poll.value = null;
quoteId = null; quoteId.value = null;
} }
function onKeydown(ev: KeyboardEvent) { function onKeydown(ev: KeyboardEvent) {
if ( if (
(ev.which === 10 || ev.which === 13) && (ev.which === 10 || ev.which === 13) &&
(ev.ctrlKey || ev.metaKey) && (ev.ctrlKey || ev.metaKey) &&
canPost canPost.value
) )
post(); post();
if (ev.which === 27) emit("esc"); if (ev.which === 27) emit("esc");
} }
function onCompositionUpdate(ev: CompositionEvent) { function onCompositionUpdate(ev: CompositionEvent) {
imeText = ev.data; imeText.value = ev.data;
} }
function onCompositionEnd(ev: CompositionEvent) { function onCompositionEnd(ev: CompositionEvent) {
imeText = ""; imeText.value = "";
} }
async function onPaste(ev: ClipboardEvent) { async function onPaste(ev: ClipboardEvent) {
for (const { item, i } of Array.from(ev.clipboardData.items).map( for (const { item, i } of Array.from(ev.clipboardData.items).map(
(item, i) => ({ item, i }) (item, i) => ({ item, i }),
)) { )) {
if (item.kind === "file") { if (item.kind === "file") {
const file = item.getAsFile(); const file = item.getAsFile();
@ -713,7 +724,7 @@ async function onPaste(ev: ClipboardEvent) {
const ext = lio >= 0 ? file.name.slice(lio) : ""; const ext = lio >= 0 ? file.name.slice(lio) : "";
const formatted = `${formatTimeString( const formatted = `${formatTimeString(
new Date(file.lastModified), new Date(file.lastModified),
defaultStore.state.pastedFileName defaultStore.state.pastedFileName,
).replace(/{{number}}/g, `${i + 1}`)}${ext}`; ).replace(/{{number}}/g, `${i + 1}`)}${ext}`;
upload(file, formatted); upload(file, formatted);
} }
@ -721,7 +732,7 @@ async function onPaste(ev: ClipboardEvent) {
const paste = ev.clipboardData.getData("text"); const paste = ev.clipboardData.getData("text");
if (!props.renote && !quoteId && paste.startsWith(url + "/notes/")) { if (!props.renote && !quoteId.value && paste.startsWith(url + "/notes/")) {
ev.preventDefault(); ev.preventDefault();
os.yesno({ os.yesno({
@ -729,11 +740,11 @@ async function onPaste(ev: ClipboardEvent) {
text: i18n.ts.quoteQuestion, text: i18n.ts.quoteQuestion,
}).then(({ canceled }) => { }).then(({ canceled }) => {
if (canceled) { if (canceled) {
insertTextAtCursor(textareaEl, paste); insertTextAtCursor(textareaEl.value, paste);
return; return;
} }
quoteId = paste quoteId.value = paste
.substring(url.length) .substring(url.length)
.match(/^\/notes\/(.+?)\/?$/)[1]; .match(/^\/notes\/(.+?)\/?$/)[1];
}); });
@ -746,7 +757,7 @@ function onDragover(ev) {
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) { if (isFile || isDriveFile) {
ev.preventDefault(); ev.preventDefault();
draghover = true; draghover.value = true;
switch (ev.dataTransfer.effectAllowed) { switch (ev.dataTransfer.effectAllowed) {
case "all": case "all":
case "uninitialized": case "uninitialized":
@ -767,15 +778,15 @@ function onDragover(ev) {
} }
function onDragenter(ev) { function onDragenter(ev) {
draghover = true; draghover.value = true;
} }
function onDragleave(ev) { function onDragleave(ev) {
draghover = false; draghover.value = false;
} }
function onDrop(ev): void { function onDrop(ev): void {
draghover = false; draghover.value = false;
// //
if (ev.dataTransfer.files.length > 0) { if (ev.dataTransfer.files.length > 0) {
@ -788,7 +799,7 @@ function onDrop(ev): void {
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== "") { if (driveFile != null && driveFile !== "") {
const file = JSON.parse(driveFile); const file = JSON.parse(driveFile);
files.push(file); files.value.push(file);
ev.preventDefault(); ev.preventDefault();
} }
//#endregion //#endregion
@ -797,16 +808,16 @@ function onDrop(ev): void {
function saveDraft() { function saveDraft() {
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}"); const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
draftData[draftKey] = { draftData[draftKey.value] = {
updatedAt: new Date(), updatedAt: new Date(),
data: { data: {
text: text, text: text.value,
useCw: useCw, useCw: useCw.value,
cw: cw, cw: cw.value,
visibility: visibility, visibility: visibility.value,
localOnly: localOnly, localOnly: localOnly.value,
files: files, files: files.value,
poll: poll, poll: poll.value,
}, },
}; };
@ -816,36 +827,37 @@ function saveDraft() {
function deleteDraft() { function deleteDraft() {
const draftData = JSON.parse(localStorage.getItem("drafts") || "{}"); const draftData = JSON.parse(localStorage.getItem("drafts") || "{}");
delete draftData[draftKey]; delete draftData[draftKey.value];
localStorage.setItem("drafts", JSON.stringify(draftData)); localStorage.setItem("drafts", JSON.stringify(draftData));
} }
async function post() { async function post() {
const processedText = preprocess(text); const processedText = preprocess(text.value);
let postData = { let postData = {
editId: props.editId ? props.editId : undefined, editId: props.editId ? props.editId : undefined,
text: processedText === "" ? undefined : processedText, text: processedText === "" ? undefined : processedText,
fileIds: files.length > 0 ? files.map((f) => f.id) : undefined, fileIds:
files.value.length > 0 ? files.value.map((f) => f.id) : undefined,
replyId: props.reply ? props.reply.id : undefined, replyId: props.reply ? props.reply.id : undefined,
renoteId: props.renote renoteId: props.renote
? props.renote.id ? props.renote.id
: quoteId : quoteId.value
? quoteId ? quoteId.value
: undefined, : undefined,
poll: poll, poll: poll.value,
cw: useCw ? cw || "" : undefined, cw: useCw.value ? cw.value || "" : undefined,
localOnly: localOnly, localOnly: localOnly.value,
visibility: visibility, visibility: visibility.value,
visibleUserIds: visibleUserIds:
visibility === "specified" visibility.value === "specified"
? visibleUsers.map((u) => u.id) ? visibleUsers.value.map((u) => u.id)
: undefined, : undefined,
}; };
if (withHashtags && hashtags && hashtags.trim() !== "") { if (withHashtags.value && hashtags.value && hashtags.value.trim() !== "") {
const hashtags_ = hashtags const hashtags_ = hashtags.value
.trim() .trim()
.split(" ") .split(" ")
.map((x) => (x.startsWith("#") ? x : "#" + x)) .map((x) => (x.startsWith("#") ? x : "#" + x))
@ -857,12 +869,14 @@ async function post() {
let token = undefined; let token = undefined;
if (postAccount) { if (postAccount.value) {
const storedAccounts = await getAccounts(); const storedAccounts = await getAccounts();
token = storedAccounts.find((x) => x.id === postAccount.id)?.token; token = storedAccounts.find(
(x) => x.id === postAccount.value.id,
)?.token;
} }
posting = true; posting.value = true;
os.api(postData.editId ? "notes/edit" : "notes/create", postData, token) os.api(postData.editId ? "notes/edit" : "notes/create", postData, token)
.then(() => { .then(() => {
clear(); clear();
@ -875,19 +889,19 @@ async function post() {
.filter((x) => x.type === "hashtag") .filter((x) => x.type === "hashtag")
.map((x) => x.props.hashtag); .map((x) => x.props.hashtag);
const history = JSON.parse( const history = JSON.parse(
localStorage.getItem("hashtags") || "[]" localStorage.getItem("hashtags") || "[]",
) as string[]; ) as string[];
localStorage.setItem( localStorage.setItem(
"hashtags", "hashtags",
JSON.stringify(unique(hashtags_.concat(history))) JSON.stringify(unique(hashtags_.concat(history))),
); );
} }
posting = false; posting.value = false;
postAccount = null; postAccount.value = null;
}); });
}) })
.catch((err) => { .catch((err) => {
posting = false; posting.value = false;
os.alert({ os.alert({
type: "error", type: "error",
text: err.message + "\n" + (err as any).id, text: err.message + "\n" + (err as any).id,
@ -901,35 +915,35 @@ function cancel() {
function insertMention() { function insertMention() {
os.selectUser().then((user) => { os.selectUser().then((user) => {
insertTextAtCursor(textareaEl, "@" + Acct.toString(user) + " "); insertTextAtCursor(textareaEl.value, "@" + Acct.toString(user) + " ");
}); });
} }
async function insertEmoji(ev: MouseEvent) { async function insertEmoji(ev: MouseEvent) {
os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl); os.openEmojiPicker(ev.currentTarget ?? ev.target, {}, textareaEl.value);
} }
async function openCheatSheet(ev: MouseEvent) { async function openCheatSheet(ev: MouseEvent) {
os.popup(XCheatSheet, {}, {}, "closed"); os.popup(XCheatSheet, {}, {}, "closed");
} }
let postAccount = $ref<misskey.entities.UserDetailed | null>(null); let postAccount = ref<misskey.entities.UserDetailed | null>(null);
function openAccountMenu(ev: MouseEvent) { function openAccountMenu(ev: MouseEvent) {
openAccountMenu_( openAccountMenu_(
{ {
withExtraOperation: false, withExtraOperation: false,
includeCurrentAccount: true, includeCurrentAccount: true,
active: postAccount != null ? postAccount.id : $i.id, active: postAccount.value != null ? postAccount.value.id : $i.id,
onChoose: (account) => { onChoose: (account) => {
if (account.id === $i.id) { if (account.id === $i.id) {
postAccount = null; postAccount.value = null;
} else { } else {
postAccount = account; postAccount.value = account;
} }
}, },
}, },
ev ev,
); );
} }
@ -943,27 +957,27 @@ onMounted(() => {
} }
// TODO: detach when unmount // TODO: detach when unmount
new Autocomplete(textareaEl, $$(text)); new Autocomplete(textareaEl.value, text);
new Autocomplete(cwInputEl, $$(cw)); new Autocomplete(cwInputEl.value, cw);
new Autocomplete(hashtagsInputEl, $$(hashtags)); new Autocomplete(hashtagsInputEl.value, hashtags);
nextTick(() => { nextTick(() => {
// 稿 // 稿
if (!props.instant && !props.mention && !props.specified) { if (!props.instant && !props.mention && !props.specified) {
const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[ const draft = JSON.parse(localStorage.getItem("drafts") || "{}")[
draftKey draftKey.value
]; ];
if (draft) { if (draft) {
text = draft.data.text; text.value = draft.data.text;
useCw = draft.data.useCw; useCw.value = draft.data.useCw;
cw = draft.data.cw; cw.value = draft.data.cw;
visibility = draft.data.visibility; visibility.value = draft.data.visibility;
localOnly = draft.data.localOnly; localOnly.value = draft.data.localOnly;
files = (draft.data.files || []).filter( files.value = (draft.data.files || []).filter(
(draftFile) => draftFile (draftFile) => draftFile,
); );
if (draft.data.poll) { if (draft.data.poll) {
poll = draft.data.poll; poll.value = draft.data.poll;
} }
} }
} }
@ -971,13 +985,13 @@ onMounted(() => {
// //
if (props.initialNote) { if (props.initialNote) {
const init = props.initialNote; const init = props.initialNote;
text = init.text ? init.text : ""; text.value = init.text ? init.text : "";
files = init.attachments ?? []; files.value = init.attachments ?? [];
cw = init.cw; cw.value = init.cw;
useCw = init.cw != null; useCw.value = init.cw != null;
if (init.poll) { if (init.poll) {
poll = { poll.value = {
choices: init.poll.options.map((x) => x.title), choices: init.poll.options.map((x) => x.title),
multiple: init.poll.multiple_choice, multiple: init.poll.multiple_choice,
expiresAt: init.poll.expires_at, expiresAt: init.poll.expires_at,
@ -985,9 +999,9 @@ onMounted(() => {
expiredAfter: null, expiredAfter: null,
}; };
} }
visibility = magLegacyVisibility(init.visibility); visibility.value = magLegacyVisibility(init.visibility);
localOnly = init.local_only ?? false; localOnly.value = init.local_only ?? false;
quoteId = init.renoted_note ? init.renoted_note.id : null; quoteId.value = init.renoted_note ? init.renoted_note.id : null;
} }
nextTick(() => watchForDraft()); nextTick(() => watchForDraft());

View File

@ -42,7 +42,7 @@ import { i18n } from "@/i18n";
export default defineComponent({ export default defineComponent({
components: { components: {
XDraggable: defineAsyncComponent(() => XDraggable: defineAsyncComponent(() =>
import("vuedraggable").then((x) => x.default) import("vuedraggable").then((x) => x.default),
), ),
MkDriveFileThumbnail, MkDriveFileThumbnail,
}, },
@ -94,7 +94,7 @@ export default defineComponent({
this.$emit( this.$emit(
"changeSensitive", "changeSensitive",
file, file,
!(file.sensitive ?? file.isSensitive) !(file.sensitive ?? file.isSensitive),
); );
}); });
}, },
@ -117,7 +117,7 @@ export default defineComponent({
async describe(file) { async describe(file) {
os.popup( os.popup(
defineAsyncComponent( defineAsyncComponent(
() => import("@/components/MkMediaCaption.vue") () => import("@/components/MkMediaCaption.vue"),
), ),
{ {
title: i18n.ts.describeFile, title: i18n.ts.describeFile,
@ -140,7 +140,7 @@ export default defineComponent({
}); });
}, },
}, },
"closed" "closed",
); );
}, },
@ -183,7 +183,7 @@ export default defineComponent({
}, },
}, },
], ],
ev.currentTarget ?? ev.target ev.currentTarget ?? ev.target,
) )
.then(() => (this.menu = null)); .then(() => (this.menu = null));
}, },

View File

@ -19,6 +19,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { shallowRef } from "vue";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import MkModal from "@/components/MkModal.vue"; import MkModal from "@/components/MkModal.vue";
import MkPostForm from "@/components/MkPostForm.vue"; import MkPostForm from "@/components/MkPostForm.vue";
@ -45,11 +47,11 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
let modal = $shallowRef<InstanceType<typeof MkModal>>(); let modal = shallowRef<InstanceType<typeof MkModal>>();
let form = $shallowRef<InstanceType<typeof MkPostForm>>(); let form = shallowRef<InstanceType<typeof MkPostForm>>();
function onPosted() { function onPosted() {
modal.close({ modal.value.close({
useSendAnimation: true, useSendAnimation: true,
}); });
} }

View File

@ -53,6 +53,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import { $i, getAccounts } from "@/account"; import { $i, getAccounts } from "@/account";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import { instance } from "@/instance"; import { instance } from "@/instance";
@ -74,12 +76,12 @@ defineProps<{
}>(); }>();
// ServiceWorker registration // ServiceWorker registration
let registration = $ref<ServiceWorkerRegistration | undefined>(); let registration = ref<ServiceWorkerRegistration | undefined>();
// If this browser supports push notification // If this browser supports push notification
let supported = $ref(false); let supported = ref(false);
// If this browser has already subscribed to push notification // If this browser has already subscribed to push notification
let pushSubscription = $ref<PushSubscription | null>(null); let pushSubscription = ref<PushSubscription | null>(null);
let pushRegistrationInServer = $ref< let pushRegistrationInServer = ref<
| { | {
state?: string; state?: string;
key?: string; key?: string;
@ -91,23 +93,24 @@ let pushRegistrationInServer = $ref<
>(); >();
function subscribe() { function subscribe() {
if (!registration || !supported || !instance.swPublickey) return; if (!registration.value || !supported.value || !instance.swPublickey)
return;
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
return promiseDialog( return promiseDialog(
registration.pushManager registration.value.pushManager
.subscribe({ .subscribe({
userVisibleOnly: true, userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array( applicationServerKey: urlBase64ToUint8Array(
instance.swPublickey instance.swPublickey,
), ),
}) })
.then( .then(
async (subscription) => { async (subscription) => {
pushSubscription = subscription; pushSubscription.value = subscription;
// Register // Register
pushRegistrationInServer = await api("sw/register", { pushRegistrationInServer.value = await api("sw/register", {
endpoint: subscription.endpoint, endpoint: subscription.endpoint,
auth: encode(subscription.getKey("auth")), auth: encode(subscription.getKey("auth")),
publickey: encode(subscription.getKey("p256dh")), publickey: encode(subscription.getKey("p256dh")),
@ -118,7 +121,7 @@ function subscribe() {
// //
if (err?.name === "NotAllowedError") { if (err?.name === "NotAllowedError") {
console.info( console.info(
"User denied the notification permission request." "User denied the notification permission request.",
); );
return; return;
} }
@ -128,20 +131,20 @@ function subscribe() {
// //
// //
await unsubscribe(); await unsubscribe();
} },
), ),
null, null,
null null,
); );
} }
async function unsubscribe() { async function unsubscribe() {
if (!pushSubscription) return; if (!pushSubscription.value) return;
const endpoint = pushSubscription.endpoint; const endpoint = pushSubscription.value.endpoint;
const accounts = await getAccounts(); const accounts = await getAccounts();
pushRegistrationInServer = undefined; pushRegistrationInServer.value = undefined;
if ($i && accounts.length >= 2) { if ($i && accounts.length >= 2) {
apiWithDialog("sw/unregister", { apiWithDialog("sw/unregister", {
@ -149,11 +152,11 @@ async function unsubscribe() {
endpoint, endpoint,
}); });
} else { } else {
pushSubscription.unsubscribe(); pushSubscription.value.unsubscribe();
apiWithDialog("sw/unregister", { apiWithDialog("sw/unregister", {
endpoint, endpoint,
}); });
pushSubscription = null; pushSubscription.value = null;
} }
} }
@ -184,20 +187,21 @@ if (navigator.serviceWorker == null) {
// TODO: // TODO:
} else { } else {
navigator.serviceWorker.ready.then(async (swr) => { navigator.serviceWorker.ready.then(async (swr) => {
registration = swr; registration.value = swr;
pushSubscription = await registration.pushManager.getSubscription(); pushSubscription.value =
await registration.value.pushManager.getSubscription();
if (instance.swPublickey && "PushManager" in window && $i && $i.token) { if (instance.swPublickey && "PushManager" in window && $i && $i.token) {
supported = true; supported.value = true;
if (pushSubscription) { if (pushSubscription.value) {
const res = await api("sw/show-registration", { const res = await api("sw/show-registration", {
endpoint: pushSubscription.endpoint, endpoint: pushSubscription.value.endpoint,
}); });
if (res) { if (res) {
pushRegistrationInServer = res; pushRegistrationInServer.value = res;
} }
} }
} }
@ -205,6 +209,6 @@ if (navigator.serviceWorker == null) {
} }
defineExpose({ defineExpose({
pushRegistrationInServer: $$(pushRegistrationInServer), pushRegistrationInServer: pushRegistrationInServer,
}); });
</script> </script>

View File

@ -37,7 +37,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, watch } from "vue"; import { onMounted, watch, ref } from "vue";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import MkUserCardMini from "@/components/MkUserCardMini.vue"; import MkUserCardMini from "@/components/MkUserCardMini.vue";
import * as os from "@/os"; import * as os from "@/os";
@ -48,26 +48,26 @@ const props = defineProps<{
noteId: string; noteId: string;
}>(); }>();
let tab = $ref<string>(); let tab = ref<string>();
let note = $ref<packed.PackNoteBase>(); let note = ref<packed.PackNoteBase>();
let reactions = $ref<types.ReactionPair[]>(); let reactions = ref<types.ReactionPair[]>();
let users = $ref<misskey.entities.UserLite[]>(); let users = ref<misskey.entities.UserLite[]>();
watch($$(tab), async () => { watch(tab, async () => {
const res = await os.api("notes/reactions", { const res = await os.api("notes/reactions", {
noteId: props.noteId, noteId: props.noteId,
type: tab, type: tab.value,
limit: 30, limit: 30,
}); });
users = res.map((x) => x.user); users.value = res.map((x) => x.user);
}); });
onMounted(() => { onMounted(() => {
os.magApi(endpoints.GetNoteById, {}, { id: props.noteId }).then((res) => { os.magApi(endpoints.GetNoteById, {}, { id: props.noteId }).then((res) => {
reactions = res.reactions; reactions.value = res.reactions;
tab = magReactionToLegacy(reactions.map((r) => r[0])[0]); tab.value = magReactionToLegacy(reactions.value.map((r) => r[0])[0]);
note = res; note.value = res;
}); });
}); });
</script> </script>

View File

@ -41,7 +41,7 @@ const props = defineProps<{
const buttonRef = ref<HTMLElement>(); const buttonRef = ref<HTMLElement>();
const canToggle = computed( const canToggle = computed(
() => !magReactionToLegacy(props.reaction).match(/@\w/) && $i () => !magReactionToLegacy(props.reaction).match(/@\w/) && $i,
); );
const toggleReaction = () => { const toggleReaction = () => {
@ -90,10 +90,10 @@ useTooltip(
targetElement: buttonRef.value, targetElement: buttonRef.value,
}, },
{}, {},
"closed" "closed",
); );
}, },
100 100,
); );
</script> </script>

View File

@ -30,7 +30,7 @@ const initialReactions = new Set(
? props.note.reactions.map((v) => magReactionToLegacy(v[0])) ? props.note.reactions.map((v) => magReactionToLegacy(v[0]))
: Object.keys(props.note.reactions) : Object.keys(props.note.reactions)
.map((v) => magConvertReaction(v)) .map((v) => magConvertReaction(v))
.map(magReactionToLegacy) .map(magReactionToLegacy),
); );
const isMe = computed(() => $i && $i.id === props.note.user.id); const isMe = computed(() => $i && $i.id === props.note.user.id);

View File

@ -85,7 +85,7 @@ const props = withDefaults(
}>(), }>(),
{ {
particle: true, particle: true,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{

View File

@ -122,7 +122,7 @@ export default defineComponent({
action: () => {}, action: () => {},
}, },
], ],
ev.currentTarget ?? ev.target ev.currentTarget ?? ev.target,
); );
}, },
}, },

View File

@ -127,7 +127,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import Vue3OtpInput from "vue3-otp-input"; import Vue3OtpInput from "vue3-otp-input";
import { defineAsyncComponent } from "vue"; import { defineAsyncComponent, ref, computed } from "vue";
import { toUnicode } from "punycode/"; import { toUnicode } from "punycode/";
import MkButton from "@/components/MkButton.vue"; import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
@ -139,24 +139,24 @@ import { login } from "@/account";
import { instance } from "@/instance"; import { instance } from "@/instance";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
let signing = $ref(false); let signing = ref(false);
let user = $ref(null); let user = ref(null);
let username = $ref(""); let username = ref("");
let password = $ref(""); let password = ref("");
let token = $ref(""); let token = ref("");
let host = $ref(toUnicode(configHost)); let host = ref(toUnicode(configHost));
let totpLogin = $ref(false); let totpLogin = ref(false);
let credential = $ref(null); let credential = ref(null);
let challengeData = $ref(null); let challengeData = ref(null);
let queryingKey = $ref(false); let queryingKey = ref(false);
let hCaptchaResponse = $ref(null); let hCaptchaResponse = ref(null);
let reCaptchaResponse = $ref(null); let reCaptchaResponse = ref(null);
const updateToken = (value: string) => { const updateToken = (value: string) => {
token = value.toString(); token.value = value.toString();
}; };
const meta = $computed(() => instance); const meta = computed(() => instance);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: "login", v: any): void; (ev: "login", v: any): void;
@ -182,14 +182,14 @@ const props = defineProps({
function onUsernameChange() { function onUsernameChange() {
os.api("users/show", { os.api("users/show", {
username: username, username: username.value,
}).then( }).then(
(userResponse) => { (userResponse) => {
user = userResponse; user.value = userResponse;
}, },
() => { () => {
user = null; user.value = null;
} },
); );
} }
@ -200,38 +200,40 @@ function onLogin(res) {
} }
function queryKey() { function queryKey() {
queryingKey = true; queryingKey.value = true;
return navigator.credentials return navigator.credentials
.get({ .get({
publicKey: { publicKey: {
challenge: byteify(challengeData.challenge, "base64"), challenge: byteify(challengeData.value.challenge, "base64"),
allowCredentials: challengeData.securityKeys.map((key) => ({ allowCredentials: challengeData.value.securityKeys.map(
id: byteify(key.id, "hex"), (key) => ({
type: "public-key", id: byteify(key.id, "hex"),
transports: ["usb", "nfc", "ble", "internal"], type: "public-key",
})), transports: ["usb", "nfc", "ble", "internal"],
}),
),
timeout: 60 * 1000, timeout: 60 * 1000,
}, },
}) })
.catch(() => { .catch(() => {
queryingKey = false; queryingKey.value = false;
return Promise.reject(null); return Promise.reject(null);
}) })
.then((credential) => { .then((credential) => {
queryingKey = false; queryingKey.value = false;
signing = true; signing.value = true;
return os.api("signin", { return os.api("signin", {
username, username: username.value,
password, password: password.value,
signature: hexify(credential.response.signature), signature: hexify(credential.response.signature),
authenticatorData: hexify( authenticatorData: hexify(
credential.response.authenticatorData credential.response.authenticatorData,
), ),
clientDataJSON: hexify(credential.response.clientDataJSON), clientDataJSON: hexify(credential.response.clientDataJSON),
credentialId: credential.id, credentialId: credential.id,
challengeId: challengeData.challengeId, challengeId: challengeData.value.challengeId,
"hcaptcha-response": hCaptchaResponse, "hcaptcha-response": hCaptchaResponse.value,
"g-recaptcha-response": reCaptchaResponse, "g-recaptcha-response": reCaptchaResponse.value,
}); });
}) })
.then((res) => { .then((res) => {
@ -244,39 +246,42 @@ function queryKey() {
type: "error", type: "error",
text: i18n.ts.signinFailed, text: i18n.ts.signinFailed,
}); });
signing = false; signing.value = false;
}); });
} }
function onSubmit() { function onSubmit() {
signing = true; signing.value = true;
console.log("submit"); console.log("submit");
if (!totpLogin && user && user.twoFactorEnabled) { if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
if (window.PublicKeyCredential && user.securityKeys) { if (window.PublicKeyCredential && user.value.securityKeys) {
os.api("signin", { os.api("signin", {
username, username: username.value,
password, password: password.value,
"hcaptcha-response": hCaptchaResponse, "hcaptcha-response": hCaptchaResponse.value,
"g-recaptcha-response": reCaptchaResponse, "g-recaptcha-response": reCaptchaResponse.value,
}) })
.then((res) => { .then((res) => {
totpLogin = true; totpLogin.value = true;
signing = false; signing.value = false;
challengeData = res; challengeData.value = res;
return queryKey(); return queryKey();
}) })
.catch(loginFailed); .catch(loginFailed);
} else { } else {
totpLogin = true; totpLogin.value = true;
signing = false; signing.value = false;
} }
} else { } else {
os.api("signin", { os.api("signin", {
username, username: username.value,
password, password: password.value,
"hcaptcha-response": hCaptchaResponse, "hcaptcha-response": hCaptchaResponse.value,
"g-recaptcha-response": reCaptchaResponse, "g-recaptcha-response": reCaptchaResponse.value,
token: user && user.twoFactorEnabled ? token : undefined, token:
user.value && user.value.twoFactorEnabled
? token.value
: undefined,
}) })
.then((res) => { .then((res) => {
emit("login", res); emit("login", res);
@ -326,9 +331,9 @@ function loginFailed(err) {
} }
} }
challengeData = null; challengeData.value = null;
totpLogin = false; totpLogin.value = false;
signing = false; signing.value = false;
} }
function resetPassword() { function resetPassword() {
@ -336,7 +341,7 @@ function resetPassword() {
defineAsyncComponent(() => import("@/components/MkForgotPassword.vue")), defineAsyncComponent(() => import("@/components/MkForgotPassword.vue")),
{}, {},
{}, {},
"closed" "closed",
); );
} }

View File

@ -12,6 +12,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import {} from "vue"; import {} from "vue";
import MkSignin from "@/components/MkSignin.vue"; import MkSignin from "@/components/MkSignin.vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
@ -25,7 +27,7 @@ const props = withDefaults(
{ {
autoSet: false, autoSet: false,
message: "", message: "",
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -34,15 +36,15 @@ const emit = defineEmits<{
(ev: "cancelled"): void; (ev: "cancelled"): void;
}>(); }>();
const dialog = $ref<InstanceType<typeof XModalWindow>>(); const dialog = ref<InstanceType<typeof XModalWindow>>();
function onClose() { function onClose() {
emit("cancelled"); emit("cancelled");
dialog.close(); dialog.value.close();
} }
function onLogin(res) { function onLogin(res) {
emit("done", res); emit("done", res);
dialog.close(); dialog.value.close();
} }
</script> </script>

View File

@ -284,6 +284,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from "vue";
import {} from "vue"; import {} from "vue";
import getPasswordStrength from "syuilo-password-strength"; import getPasswordStrength from "syuilo-password-strength";
import { toUnicode } from "punycode/"; import { toUnicode } from "punycode/";
@ -303,7 +305,7 @@ const props = withDefaults(
}>(), }>(),
{ {
autoSet: false, autoSet: false,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -313,14 +315,14 @@ const emit = defineEmits<{
const host = toUnicode(config.host); const host = toUnicode(config.host);
let hcaptcha = $ref(); let hcaptcha = ref();
let recaptcha = $ref(); let recaptcha = ref();
let username: string = $ref(""); let username: string = ref("");
let password: string = $ref(""); let password: string = ref("");
let retypedPassword: string = $ref(""); let retypedPassword: string = ref("");
let invitationCode: string = $ref(""); let invitationCode: string = ref("");
let email = $ref(""); let email = ref("");
let usernameState: let usernameState:
| null | null
| "wait" | "wait"
@ -329,8 +331,8 @@ let usernameState:
| "error" | "error"
| "invalid-format" | "invalid-format"
| "min-range" | "min-range"
| "max-range" = $ref(null); | "max-range" = ref(null);
let invitationState: null | "entered" = $ref(null); let invitationState: null | "entered" = ref(null);
let emailState: let emailState:
| null | null
| "wait" | "wait"
@ -341,141 +343,142 @@ let emailState:
| "unavailable:mx" | "unavailable:mx"
| "unavailable:smtp" | "unavailable:smtp"
| "unavailable" | "unavailable"
| "error" = $ref(null); | "error" = ref(null);
let passwordStrength: "" | "low" | "medium" | "high" = $ref(""); let passwordStrength: "" | "low" | "medium" | "high" = ref("");
let passwordRetypeState: null | "match" | "not-match" = $ref(null); let passwordRetypeState: null | "match" | "not-match" = ref(null);
let submitting: boolean = $ref(false); let submitting: boolean = ref(false);
let ToSAgreement: boolean = $ref(false); let ToSAgreement: boolean = ref(false);
let hCaptchaResponse = $ref(null); let hCaptchaResponse = ref(null);
let reCaptchaResponse = $ref(null); let reCaptchaResponse = ref(null);
const shouldDisableSubmitting = $computed((): boolean => { const shouldDisableSubmitting = computed((): boolean => {
return ( return (
submitting || submitting.value ||
(instance.tosUrl && !ToSAgreement) || (instance.tosUrl && !ToSAgreement.value) ||
(instance.enableHcaptcha && !hCaptchaResponse) || (instance.enableHcaptcha && !hCaptchaResponse.value) ||
(instance.enableRecaptcha && !reCaptchaResponse) || (instance.enableRecaptcha && !reCaptchaResponse.value) ||
passwordRetypeState === "not-match" passwordRetypeState.value === "not-match"
); );
}); });
function onChangeInvitationCode(): void { function onChangeInvitationCode(): void {
if (invitationCode === "") { if (invitationCode.value === "") {
invitationState = null; invitationState.value = null;
return; return;
} }
invitationState = "entered"; invitationState.value = "entered";
} }
function onChangeUsername(): void { function onChangeUsername(): void {
if (username === "") { if (username.value === "") {
usernameState = null; usernameState.value = null;
return; return;
} }
{ {
const err = !username.match(/^[a-zA-Z0-9_]+$/) const err = !username.value.match(/^[a-zA-Z0-9_]+$/)
? "invalid-format" ? "invalid-format"
: username.length < 1 : username.value.length < 1
? "min-range" ? "min-range"
: username.length > 20 : username.value.length > 20
? "max-range" ? "max-range"
: null; : null;
if (err) { if (err) {
usernameState = err; usernameState.value = err;
return; return;
} }
} }
usernameState = "wait"; usernameState.value = "wait";
os.api("username/available", { os.api("username/available", {
username, username: username.value,
}) })
.then((result) => { .then((result) => {
usernameState = result.available ? "ok" : "unavailable"; usernameState.value = result.available ? "ok" : "unavailable";
}) })
.catch(() => { .catch(() => {
usernameState = "error"; usernameState.value = "error";
}); });
} }
function onChangeEmail(): void { function onChangeEmail(): void {
if (email === "") { if (email.value === "") {
emailState = null; emailState.value = null;
return; return;
} }
emailState = "wait"; emailState.value = "wait";
os.api("email-address/available", { os.api("email-address/available", {
emailAddress: email, emailAddress: email.value,
}) })
.then((result) => { .then((result) => {
emailState = result.available emailState.value = result.available
? "ok" ? "ok"
: result.reason === "used" : result.reason === "used"
? "unavailable:used" ? "unavailable:used"
: result.reason === "format" : result.reason === "format"
? "unavailable:format" ? "unavailable:format"
: result.reason === "disposable" : result.reason === "disposable"
? "unavailable:disposable" ? "unavailable:disposable"
: result.reason === "mx" : result.reason === "mx"
? "unavailable:mx" ? "unavailable:mx"
: result.reason === "smtp" : result.reason === "smtp"
? "unavailable:smtp" ? "unavailable:smtp"
: "unavailable"; : "unavailable";
}) })
.catch(() => { .catch(() => {
emailState = "error"; emailState.value = "error";
}); });
} }
function onChangePassword(): void { function onChangePassword(): void {
if (password === "") { if (password.value === "") {
passwordStrength = ""; passwordStrength.value = "";
return; return;
} }
const strength = getPasswordStrength(password); const strength = getPasswordStrength(password.value);
passwordStrength = passwordStrength.value =
strength > 0.7 ? "high" : strength > 0.3 ? "medium" : "low"; strength > 0.7 ? "high" : strength > 0.3 ? "medium" : "low";
} }
function onChangePasswordRetype(): void { function onChangePasswordRetype(): void {
if (retypedPassword === "") { if (retypedPassword.value === "") {
passwordRetypeState = null; passwordRetypeState.value = null;
return; return;
} }
passwordRetypeState = password === retypedPassword ? "match" : "not-match"; passwordRetypeState.value =
password.value === retypedPassword.value ? "match" : "not-match";
} }
function onSubmit(): void { function onSubmit(): void {
if (submitting) return; if (submitting.value) return;
submitting = true; submitting.value = true;
os.api("signup", { os.api("signup", {
username, username: username.value,
password, password: password.value,
emailAddress: email, emailAddress: email.value,
invitationCode, invitationCode: invitationCode.value,
"hcaptcha-response": hCaptchaResponse, "hcaptcha-response": hCaptchaResponse.value,
"g-recaptcha-response": reCaptchaResponse, "g-recaptcha-response": reCaptchaResponse.value,
}) })
.then(() => { .then(() => {
if (instance.emailRequiredForSignup) { if (instance.emailRequiredForSignup) {
os.alert({ os.alert({
type: "success", type: "success",
title: i18n.ts._signup.almostThere, title: i18n.ts._signup.almostThere,
text: i18n.t("_signup.emailSent", { email }), text: i18n.t("_signup.emailSent", { email: email.value }),
}); });
emit("signupEmailPending"); emit("signupEmailPending");
} else { } else {
os.api("signin", { os.api("signin", {
username, username: username.value,
password, password: password.value,
}).then((res) => { }).then((res) => {
emit("signup", res); emit("signup", res);
@ -486,9 +489,9 @@ function onSubmit(): void {
} }
}) })
.catch(() => { .catch(() => {
submitting = false; submitting.value = false;
hcaptcha.reset?.(); hcaptcha.value.reset?.();
recaptcha.reset?.(); recaptcha.value.reset?.();
os.alert({ os.alert({
type: "error", type: "error",

View File

@ -20,6 +20,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue";
import {} from "vue"; import {} from "vue";
import XSignup from "@/components/MkSignup.vue"; import XSignup from "@/components/MkSignup.vue";
import XModalWindow from "@/components/MkModalWindow.vue"; import XModalWindow from "@/components/MkModalWindow.vue";
@ -31,7 +33,7 @@ const props = withDefaults(
}>(), }>(),
{ {
autoSet: false, autoSet: false,
} },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@ -39,14 +41,14 @@ const emit = defineEmits<{
(ev: "closed"): void; (ev: "closed"): void;
}>(); }>();
const dialog = $ref<InstanceType<typeof XModalWindow>>(); const dialog = ref<InstanceType<typeof XModalWindow>>();
function onSignup(res) { function onSignup(res) {
emit("done", res); emit("done", res);
dialog?.close(); dialog.value?.close();
} }
function onSignupEmailPending() { function onSignupEmailPending() {
dialog?.close(); dialog.value?.close();
} }
</script> </script>

View File

@ -105,13 +105,16 @@ onMounted(() => {
particles.value.push(particle); particles.value.push(particle);
window.setTimeout(() => { window.setTimeout(() => {
particles.value = particles.value.filter( particles.value = particles.value.filter(
(x) => x.id !== particle.id (x) => x.id !== particle.id,
); );
}, particle.dur - 100); }, particle.dur - 100);
window.setTimeout(() => { window.setTimeout(
add(); () => {
}, 500 + Math.random() * 500); add();
},
500 + Math.random() * 500,
);
}; };
add(); add();
} }

View File

@ -104,7 +104,7 @@ useTooltip(buttonRef, async (showing) => {
targetElement: buttonRef.value, targetElement: buttonRef.value,
}, },
{}, {},
"closed" "closed",
); );
}); });
</script> </script>

View File

@ -37,13 +37,13 @@ export default defineComponent({
onClick: () => { onClick: () => {
this.$emit( this.$emit(
"update:modelValue", "update:modelValue",
option.props?.value option.props?.value,
); );
}, },
}, },
option.children option.children,
) ),
) ),
); );
}, },
}); });

Some files were not shown because too many files have changed in this diff Show More