diff --git a/locales/en-US.yml b/locales/en-US.yml index 173404e7c4..97f5313c80 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1400,6 +1400,7 @@ _profile: metadataContent: "Content" changeAvatar: "Change avatar" changeBanner: "Change banner" + locationDescription: "If entered properly, this will display your local time to other users." _exportOrImport: allNotes: "All posts" followingList: "Followed users" diff --git a/package.json b/package.json index d1173a5d40..ef22dab20d 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "calckey", - "version": "13.2.0-dev10", + "version": "13.2.0-dev11", "codename": "aqua", "repository": { "type": "git", "url": "https://codeberg.org/calckey/calckey.git" }, - "packageManager": "pnpm@7.27.0", + "packageManager": "pnpm@7.27.1", "private": true, "scripts": { "rebuild": "pnpm run clean && pnpm -r run build && pnpm run gulp", diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 10ab5c66f5..a0945ae7b1 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -122,7 +122,6 @@ export async function createNote( } logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - logger.info(`Creating the Note: ${note.id}`); // Skip if note is made before 2007 (1yr before Fedi was created) diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts index cc60a8db9b..9a690e19f0 100644 --- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts +++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts @@ -54,7 +54,7 @@ export function apiMastodonCompatible(router: Router): void { // displayed without being logged in try { const data = await client.getInstance(); - ctx.body = getInstance(data.data); + ctx.body = await getInstance(data.data); } catch (e: any) { console.error(e); ctx.status = 401; diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index f395c5a9c5..5b7df948e3 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -98,20 +98,23 @@ export function apiAccountMastodon(router: Router): void { ctx.body = e.response.data; } }); - router.get<{ Params: { id: string } }>("/v1/accounts/:id", async (ctx) => { - const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; - const accessTokens = ctx.headers.authorization; - const client = getClient(BASE_URL, accessTokens); - try { - const data = await client.getAccount(ctx.params.id); - ctx.body = data.data; - } catch (e: any) { - console.error(e); - console.error(e.response.data); - ctx.status = 401; - ctx.body = e.response.data; - } - }); + router.get<{ Params: { id: string } }>( + "/v1/accounts/:id(^.*\\d.*$)", + async (ctx) => { + const BASE_URL = `${ctx.protocol}://${ctx.hostname}`; + const accessTokens = ctx.headers.authorization; + const client = getClient(BASE_URL, accessTokens); + try { + const data = await client.getAccount(ctx.params.id); + ctx.body = data.data; + } catch (e: any) { + console.error(e); + console.error(e.response.data); + ctx.status = 401; + ctx.body = e.response.data; + } + }, + ); router.get<{ Params: { id: string } }>( "/v1/accounts/:id/statuses", async (ctx) => { @@ -304,11 +307,12 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); let users; try { - const idsRaw = ctx.request.body ? ["id[]"] : null; + // TODO: this should be body + const idsRaw = ctx.request.query ? ctx.request.query["id[]"] : null; const ids = typeof idsRaw === "string" ? [idsRaw] : idsRaw; users = ids; relationshopModel.id = idsRaw?.toString() || "1"; - if (!(idsRaw && ids)) { + if (!idsRaw) { ctx.body = [relationshopModel]; return; } @@ -316,9 +320,11 @@ export function apiAccountMastodon(router: Router): void { ctx.body = data.data; } catch (e: any) { console.error(e); - console.error(e.response.data); + let data = e.response.data; + data.users = users; + console.error(data); ctx.status = 401; - ctx.body = e.response.data; + ctx.body = data; } }); router.get("/v1/bookmarks", async (ctx) => { diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index a67edc5b2f..5fba6f8a63 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -1,6 +1,9 @@ import { Entity } from "@calckey/megalodon"; +import { fetchMeta } from "@/misc/fetch-meta.js"; + // TODO: add calckey features -export function getInstance(response: Entity.Instance) { +export async function getInstance(response: Entity.Instance) { + const meta = await fetchMeta(true); return { uri: response.uri, title: response.title || "", @@ -11,8 +14,8 @@ export function getInstance(response: Entity.Instance) { urls: response.urls, stats: response.stats, thumbnail: response.thumbnail || "", - languages: ["en", "de", "ja"], - registrations: response.registrations, + languages: meta.langs, + registrations: !meta.disableRegistration || response.registrations, approval_required: !response.registrations, invites_enabled: response.registrations, configuration: { @@ -77,17 +80,17 @@ export function getInstance(response: Entity.Instance) { bot: true, discoverable: false, group: false, - created_at: "1971-01-01T00:00:00.000Z", - note: "", - url: "https://http.cat/404", - avatar: "https://http.cat/404", - avatar_static: "https://http.cat/404", + created_at: Math.floor(new Date().getTime() / 1000), + note: "Please refer to the original instance for the actual admin contact.", + url: "/", + avatar: "/static-assets/badges/info.png", + avatar_static: "/static-assets/badges/info.png", header: "https://http.cat/404", header_static: "https://http.cat/404", followers_count: -1, following_count: 0, statuses_count: 0, - last_status_at: "1971-01-01T00:00:00.000Z", + last_status_at: Math.floor(new Date().getTime() / 1000), noindex: true, emojis: [], fields: [], diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index 2be79c73a7..a72ac2c7e0 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -404,7 +404,7 @@ async function getFirstReaction( ) { const accessTokenArr = accessTokens?.split(" ") ?? [null]; const accessToken = accessTokenArr[accessTokenArr.length - 1]; - let react = "👍"; + let react = "⭐"; try { const api = await axios.post(`${BASE_URL}/api/i/registry/get-unsecure`, { scope: ["client", "base"], @@ -412,7 +412,7 @@ async function getFirstReaction( i: accessToken, }); const reactRaw = api.data; - react = Array.isArray(reactRaw) ? api.data[0] : "👍"; + react = Array.isArray(reactRaw) ? api.data[0] : "⭐"; console.log(api.data); return react; } catch (e) { @@ -426,16 +426,16 @@ export function statusModel( emojis: MastodonEntity.Emoji[], content: string, ) { - const now = "1970-01-02T00:00:00.000Z"; + const now = Math.floor(new Date().getTime() / 1000); return { id: "9atm5frjhb", uri: "https://http.cat/404", // "" url: "https://http.cat/404", // "", account: { id: "9arzuvv0sw", - username: "ReactionBot", - acct: "ReactionBot", - display_name: "ReactionsToThisPost", + username: "Reactions", + acct: "Reactions", + display_name: "Reactions to this post", locked: false, created_at: now, followers_count: 0, @@ -443,8 +443,8 @@ export function statusModel( statuses_count: 0, note: "", url: "https://http.cat/404", - avatar: "https://http.cat/404", - avatar_static: "https://http.cat/404", + avatar: "/static-assets/badges/info.png", + avatar_static: "/static-assets/badges/info.png", header: "https://http.cat/404", // "" header_static: "https://http.cat/404", // "" emojis: [], diff --git a/packages/backend/src/server/web/bios.js b/packages/backend/src/server/web/bios.js index 1acdafd1d5..e715a01068 100644 --- a/packages/backend/src/server/web/bios.js +++ b/packages/backend/src/server/web/bios.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; window.onload = async () => { - const account = JSON.parse(localStorage.getItem('account')); + const account = JSON.parse(localStorage.getItem("account")); const i = account.token; const api = (endpoint, data = {}) => { @@ -10,42 +10,44 @@ window.onload = async () => { if (i) data.i = i; // Send request - fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, { - method: 'POST', + fetch(endpoint.indexOf("://") > -1 ? endpoint : `/api/${endpoint}`, { + method: "POST", body: JSON.stringify(data), - credentials: 'omit', - cache: 'no-cache' - }).then(async (res) => { - const body = res.status === 204 ? null : await res.json(); + credentials: "omit", + cache: "no-cache", + }) + .then(async (res) => { + const body = res.status === 204 ? null : await res.json(); - if (res.status === 200) { - resolve(body); - } else if (res.status === 204) { - resolve(); - } else { - reject(body.error); - } - }).catch(reject); + if (res.status === 200) { + resolve(body); + } else if (res.status === 204) { + resolve(); + } else { + reject(body.error); + } + }) + .catch(reject); }); return promise; }; - const content = document.getElementById('content'); + const content = document.getElementById("content"); - document.getElementById('ls').addEventListener('click', () => { - content.innerHTML = ''; + document.getElementById("ls").addEventListener("click", () => { + content.innerHTML = ""; - const lsEditor = document.createElement('div'); - lsEditor.id = 'lsEditor'; + const lsEditor = document.createElement("div"); + lsEditor.id = "lsEditor"; - const adder = document.createElement('div'); - adder.classList.add('adder'); - const addKeyInput = document.createElement('input'); - const addValueTextarea = document.createElement('textarea'); - const addButton = document.createElement('button'); - addButton.textContent = 'Add'; - addButton.addEventListener('click', () => { + const adder = document.createElement("div"); + adder.classList.add("adder"); + const addKeyInput = document.createElement("input"); + const addValueTextarea = document.createElement("textarea"); + const addButton = document.createElement("button"); + addButton.textContent = "Add"; + addButton.addEventListener("click", () => { localStorage.setItem(addKeyInput.value, addValueTextarea.value); location.reload(); }); @@ -57,21 +59,21 @@ window.onload = async () => { for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); - const record = document.createElement('div'); - record.classList.add('record'); - const header = document.createElement('header'); + const record = document.createElement("div"); + record.classList.add("record"); + const header = document.createElement("header"); header.textContent = k; - const textarea = document.createElement('textarea'); + const textarea = document.createElement("textarea"); textarea.textContent = localStorage.getItem(k); - const saveButton = document.createElement('button'); - saveButton.textContent = 'Save'; - saveButton.addEventListener('click', () => { + const saveButton = document.createElement("button"); + saveButton.textContent = "Save"; + saveButton.addEventListener("click", () => { localStorage.setItem(k, textarea.value); location.reload(); }); - const removeButton = document.createElement('button'); - removeButton.textContent = 'Remove'; - removeButton.addEventListener('click', () => { + const removeButton = document.createElement("button"); + removeButton.textContent = "Remove"; + removeButton.addEventListener("click", () => { localStorage.removeItem(k); location.reload(); }); diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index f4e0707a93..e7e859d20c 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -9,120 +9,122 @@ * 注: webpackは介さないため、このファイルではrequireやimportは使えません。 */ -'use strict'; +"use strict"; // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので (async () => { window.onerror = (e) => { console.error(e); - renderError('SOMETHING_HAPPENED', e); + renderError("SOMETHING_HAPPENED", e); }; window.onunhandledrejection = (e) => { console.error(e); - renderError('SOMETHING_HAPPENED_IN_PROMISE', e); + renderError("SOMETHING_HAPPENED_IN_PROMISE", e); }; //#region Detect language & fetch translations - const v = localStorage.getItem('v') || VERSION; + const v = localStorage.getItem("v") || VERSION; const supportedLangs = LANGS; - let lang = localStorage.getItem('lang'); + let lang = localStorage.getItem("lang"); if (lang == null || !supportedLangs.includes(lang)) { if (supportedLangs.includes(navigator.language)) { lang = navigator.language; } else { - lang = supportedLangs.find(x => x.split('-')[0] === navigator.language); + lang = supportedLangs.find((x) => x.split("-")[0] === navigator.language); // Fallback - if (lang == null) lang = 'en-US'; + if (lang == null) lang = "en-US"; } } const res = await fetch(`/assets/locales/${lang}.${v}.json`); if (res.status === 200) { - localStorage.setItem('lang', lang); - localStorage.setItem('locale', await res.text()); - localStorage.setItem('localeVersion', v); + localStorage.setItem("lang", lang); + localStorage.setItem("locale", await res.text()); + localStorage.setItem("localeVersion", v); } else { await checkUpdate(); - renderError('LOCALE_FETCH'); + renderError("LOCALE_FETCH"); return; } //#endregion //#region Script function importAppScript() { - import(`/assets/${CLIENT_ENTRY}`) - .catch(async e => { - await checkUpdate(); - console.error(e); - renderError('APP_IMPORT', e); - }); + import(`/assets/${CLIENT_ENTRY}`).catch(async (e) => { + await checkUpdate(); + console.error(e); + renderError("APP_IMPORT", e); + }); } // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある - if (document.readyState !== 'loading') { + if (document.readyState !== "loading") { importAppScript(); } else { - window.addEventListener('DOMContentLoaded', () => { + window.addEventListener("DOMContentLoaded", () => { importAppScript(); }); } //#endregion //#region Theme - const theme = localStorage.getItem('theme'); + const theme = localStorage.getItem("theme"); if (theme) { for (const [k, v] of Object.entries(JSON.parse(theme))) { document.documentElement.style.setProperty(`--${k}`, v.toString()); // HTMLの theme-color 適用 - if (k === 'htmlThemeColor') { + if (k === "htmlThemeColor") { for (const tag of document.head.children) { - if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { - tag.setAttribute('content', v); + if ( + tag.tagName === "META" && + tag.getAttribute("name") === "theme-color" + ) { + tag.setAttribute("content", v); break; } } } } } - const colorSchema = localStorage.getItem('colorSchema'); + const colorSchema = localStorage.getItem("colorSchema"); if (colorSchema) { - document.documentElement.style.setProperty('color-schema', colorSchema); + document.documentElement.style.setProperty("color-schema", colorSchema); } //#endregion - const fontSize = localStorage.getItem('fontSize'); + const fontSize = localStorage.getItem("fontSize"); if (fontSize) { - document.documentElement.classList.add('f-' + fontSize); + document.documentElement.classList.add("f-" + fontSize); } - const useSystemFont = localStorage.getItem('useSystemFont'); + const useSystemFont = localStorage.getItem("useSystemFont"); if (useSystemFont) { - document.documentElement.classList.add('useSystemFont'); + document.documentElement.classList.add("useSystemFont"); } - const wallpaper = localStorage.getItem('wallpaper'); + const wallpaper = localStorage.getItem("wallpaper"); if (wallpaper) { document.documentElement.style.backgroundImage = `url(${wallpaper})`; } - const customCss = localStorage.getItem('customCss'); + const customCss = localStorage.getItem("customCss"); if (customCss && customCss.length > 0) { - const style = document.createElement('style'); + const style = document.createElement("style"); style.innerHTML = customCss; document.head.appendChild(style); } async function addStyle(styleText) { - let css = document.createElement('style'); + let css = document.createElement("style"); css.appendChild(document.createTextNode(styleText)); document.head.appendChild(css); } function renderError(code, details) { - let errorsElement = document.getElementById('errors'); + let errorsElement = document.getElementById("errors"); if (!errorsElement) { document.body.innerHTML = ` @@ -158,9 +160,9 @@
`; - errorsElement = document.getElementById('errors'); + errorsElement = document.getElementById("errors"); } - const detailsElement = document.createElement('details'); + const detailsElement = document.createElement("details"); detailsElement.innerHTML = `
@@ -278,25 +280,25 @@ details { width: 50%; } - `) + `); } async function checkUpdate() { try { - const res = await fetch('/api/meta', { - method: 'POST', - cache: 'no-cache' + const res = await fetch("/api/meta", { + method: "POST", + cache: "no-cache", }); const meta = await res.json(); if (meta.version != v) { - localStorage.setItem('v', meta.version); + localStorage.setItem("v", meta.version); refresh(); } } catch (e) { console.error(e); - renderError('UPDATE_CHECK', e); + renderError("UPDATE_CHECK", e); throw e; } } @@ -304,9 +306,9 @@ function refresh() { // Clear cache (service worker) try { - navigator.serviceWorker.controller.postMessage('clear'); - navigator.serviceWorker.getRegistrations().then(registrations => { - registrations.forEach(registration => registration.unregister()); + navigator.serviceWorker.controller.postMessage("clear"); + navigator.serviceWorker.getRegistrations().then((registrations) => { + registrations.forEach((registration) => registration.unregister()); }); } catch (e) { console.error(e); diff --git a/packages/backend/src/server/web/cli.js b/packages/backend/src/server/web/cli.js index 3dff1d4860..290453f7e0 100644 --- a/packages/backend/src/server/web/cli.js +++ b/packages/backend/src/server/web/cli.js @@ -1,51 +1,53 @@ -'use strict'; +"use strict"; window.onload = async () => { - const account = JSON.parse(localStorage.getItem('account')); + const account = JSON.parse(localStorage.getItem("account")); const i = account.token; const api = (endpoint, data = {}) => { const promise = new Promise((resolve, reject) => { // Append a credential if (i) data.i = i; - + // Send request - fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, { - method: 'POST', + fetch(endpoint.indexOf("://") > -1 ? endpoint : `/api/${endpoint}`, { + method: "POST", body: JSON.stringify(data), - credentials: 'omit', - cache: 'no-cache' - }).then(async (res) => { - const body = res.status === 204 ? null : await res.json(); - - if (res.status === 200) { - resolve(body); - } else if (res.status === 204) { - resolve(); - } else { - reject(body.error); - } - }).catch(reject); + credentials: "omit", + cache: "no-cache", + }) + .then(async (res) => { + const body = res.status === 204 ? null : await res.json(); + + if (res.status === 200) { + resolve(body); + } else if (res.status === 204) { + resolve(); + } else { + reject(body.error); + } + }) + .catch(reject); }); - + return promise; }; - document.getElementById('submit').addEventListener('click', () => { - api('notes/create', { - text: document.getElementById('text').value + document.getElementById("submit").addEventListener("click", () => { + api("notes/create", { + text: document.getElementById("text").value, }).then(() => { location.reload(); }); }); - api('notes/timeline').then(notes => { - const tl = document.getElementById('tl'); + api("notes/timeline").then((notes) => { + const tl = document.getElementById("tl"); for (const note of notes) { - const el = document.createElement('div'); - const name = document.createElement('header'); + const el = document.createElement("div"); + const name = document.createElement("header"); name.textContent = `${note.user.name} @${note.user.username}`; - const text = document.createElement('div'); + const text = document.createElement("div"); text.textContent = `${note.text}`; el.appendChild(name); el.appendChild(text); diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug index b2dc5f63fd..25eac09423 100644 --- a/packages/backend/src/server/web/views/note.pug +++ b/packages/backend/src/server/web/views/note.pug @@ -7,7 +7,7 @@ block vars - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; - const isImage = note.files.length !== 0 && note.files[0].type.startsWith('image'); - const isVideo = note.files.length !== 0 && note.files[0].type.startsWith('video'); - - const imageUrl = isImage ? note.files[0].url : isVideo ? note.files[0].thumbnailUrl : avatarUrl; + - const imageUrl = isImage ? note.files[0].url : isVideo ? note.files[0].thumbnailUrl : avatarUrl; block title = `${title} | ${instanceName}` @@ -23,7 +23,7 @@ block og meta(property='og:description' content= summary) meta(property='og:url' content= url) meta(property='og:image' content= imageUrl) - if isImage + if isImage && !note.files[0].isSensitive meta(property='og:image:width' content=note.files[0].properties.width) meta(property='og:image:height' content=note.files[0].properties.height) meta(property='og:image:type' content=note.files[0].type) diff --git a/packages/client/package.json b/packages/client/package.json index fe6288406b..4289d80497 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -6,13 +6,9 @@ "build": "pnpm vite build", "lint": "pnpm rome check \"src/**/*.{ts,vue}\"" }, - "dependencies": { - "@khmyznikov/pwa-install": "^0.2.0", - "chartjs-chart-matrix": "^2.0.1", - "gsap": "^3.11.4" - }, "devDependencies": { "@discordapp/twemoji": "14.0.2", + "@khmyznikov/pwa-install": "^0.2.0", "@rollup/plugin-alias": "3.1.9", "@rollup/plugin-json": "4.1.0", "@rollup/pluginutils": "^4.2.1", @@ -40,6 +36,8 @@ "chartjs-adapter-date-fns": "2.0.1", "chartjs-plugin-gradient": "0.5.1", "chartjs-plugin-zoom": "1.2.1", + "chartjs-chart-matrix": "^2.0.1", + "city-timezones": "^1.2.1", "compare-versions": "5.0.3", "cropperjs": "2.0.0-beta.2", "cross-env": "7.0.3", @@ -47,6 +45,7 @@ "date-fns": "2.29.3", "escape-regexp": "0.0.1", "eventemitter3": "4.0.7", + "gsap": "^3.11.4", "idb-keyval": "6.2.0", "insert-text-at-cursor": "0.3.0", "json5": "2.2.3", diff --git a/packages/client/src/components/MkDialog.vue b/packages/client/src/components/MkDialog.vue index e01b0a3325..1bcfa81a5f 100644 --- a/packages/client/src/components/MkDialog.vue +++ b/packages/client/src/components/MkDialog.vue @@ -1,51 +1,51 @@ - diff --git a/packages/client/src/components/MkModal.vue b/packages/client/src/components/MkModal.vue index ed0e7f59dd..80cfaf8da4 100644 --- a/packages/client/src/components/MkModal.vue +++ b/packages/client/src/components/MkModal.vue @@ -1,12 +1,19 @@ - diff --git a/packages/client/src/components/MkModalWindow.vue b/packages/client/src/components/MkModalWindow.vue index 6663824666..2765a99270 100644 --- a/packages/client/src/components/MkModalWindow.vue +++ b/packages/client/src/components/MkModalWindow.vue @@ -1,19 +1,19 @@ diff --git a/packages/client/src/components/MkUpdated.vue b/packages/client/src/components/MkUpdated.vue index bd88821956..0ffe2edeab 100644 --- a/packages/client/src/components/MkUpdated.vue +++ b/packages/client/src/components/MkUpdated.vue @@ -1,20 +1,21 @@ - diff --git a/packages/client/src/components/MkWaitingDialog.vue b/packages/client/src/components/MkWaitingDialog.vue index dfc5115cad..00d34e9495 100644 --- a/packages/client/src/components/MkWaitingDialog.vue +++ b/packages/client/src/components/MkWaitingDialog.vue @@ -1,18 +1,18 @@ - diff --git a/packages/client/src/components/global/MkLoading.vue b/packages/client/src/components/global/MkLoading.vue index 362484f5fc..dc61e577b1 100644 --- a/packages/client/src/components/global/MkLoading.vue +++ b/packages/client/src/components/global/MkLoading.vue @@ -15,10 +15,12 @@ const props = withDefaults(defineProps<{ inline?: boolean; colored?: boolean; mini?: boolean; + em?: boolean; }>(), { inline: false, colored: true, mini: false, + em: false, }); @@ -70,6 +72,12 @@ const props = withDefaults(defineProps<{ padding: 16px; --size: 32px; } + &.em { + display: inline-block; + vertical-align: middle; + padding: 0; + --size: 1em; + } } .container { diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue index c25f90dba8..fce2c836c4 100644 --- a/packages/client/src/components/global/MkPageHeader.vue +++ b/packages/client/src/components/global/MkPageHeader.vue @@ -141,32 +141,32 @@ const calcBg = () => { let ro: ResizeObserver | null; onMounted(() => { - calcBg(); - globalEvents.on('themeChanged', calcBg); + calcBg(); + globalEvents.on('themeChanged', calcBg); - const updateTabHighlight = () => { - nextTick(() => { - const tabEl = tabRefs[props.tab]; - if (tabEl && tabHighlightEl) { - const tabSizeX = tabEl.scrollWidth + 20; - tabEl.style.setProperty('--width', `${tabSizeX}px`); - - const parentRect = tabsEl.getBoundingClientRect(); - const rect = tabEl.getBoundingClientRect(); - const left = (rect.left - parentRect.left + tabsEl.scrollLeft); - tabHighlightEl.style.width = `${tabSizeX}px`; - tabHighlightEl.style.transform = `translateX(${left}px)`; - tabsEl.scrollTo({left: left - 60, behavior: "smooth"}); - } - }); - }; - - const updateTab = () => { - emit('update:tab', props.tab); - }; - - watch(() => [props.tab, props.tabs], updateTabHighlight, { immediate: true }); - watch(() => props.tab, updateTab); + watch(() => [props.tab, props.tabs], () => { + nextTick(() => { + const tabEl = tabRefs[props.tab]; + if (tabEl && tabHighlightEl) { + // offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある + // https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4 + const tabSizeX = tabEl.scrollWidth + 20; // + the tab's padding + tabEl.style = `--width: ${tabSizeX}px`; + setTimeout(() => { + const parentRect = tabsEl.getBoundingClientRect(); + const rect = tabEl.getBoundingClientRect(); + const left = (rect.left - parentRect.left + tabsEl?.scrollLeft); + tabHighlightEl.style.width = tabSizeX + 'px'; + tabHighlightEl.style.transform = `translateX(${left}px)`; + window.requestAnimationFrame(() => { + tabsEl?.scrollTo({left: left - 60, behavior: "smooth"}); + }) + }, 200); + } + }); + }, { + immediate: true, + }); if (el && el.parentElement) { narrow.value = el.parentElement.offsetWidth < 500; diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index e6bde24d1a..935fa6fefc 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -251,6 +251,7 @@ import { getAccountFromId } from "@/scripts/get-account-from-id"; // クライアントが更新されたか? const lastVersion = localStorage.getItem("lastVersion"); + if (lastVersion !== version) { localStorage.setItem("lastVersion", version); diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index b25a3431b2..0e6bdf3182 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -7,6 +7,8 @@ import * as Misskey from "calckey-js"; import { apiUrl, url } from "@/config"; import MkPostFormDialog from "@/components/MkPostFormDialog.vue"; import MkWaitingDialog from "@/components/MkWaitingDialog.vue"; +import MkToast from "@/components/MkToast.vue"; +import MkDialog from "@/components/MkDialog.vue"; import { MenuItem } from "@/types/menu"; import { $i } from "@/account"; @@ -247,7 +249,7 @@ export function modalPageWindow(path: string) { export function toast(message: string) { popup( - defineAsyncComponent(() => import("@/components/MkToast.vue")), + MkToast, { message, }, @@ -263,7 +265,7 @@ export function alert(props: { }): Promise { return new Promise((resolve, reject) => { popup( - defineAsyncComponent(() => import("@/components/MkDialog.vue")), + MkDialog, props, { done: (result) => { @@ -279,10 +281,12 @@ export function confirm(props: { type: "error" | "info" | "success" | "warning" | "waiting" | "question"; title?: string | null; text?: string | null; + okText?: string; + cancelText?: string; }): Promise<{ canceled: boolean }> { return new Promise((resolve, reject) => { popup( - defineAsyncComponent(() => import("@/components/MkDialog.vue")), + MkDialog, { ...props, showCancelButton: true, diff --git a/packages/client/src/pages/admin/integrations.twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue deleted file mode 100644 index 95a11eb821..0000000000 --- a/packages/client/src/pages/admin/integrations.twitter.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index 540eced4d4..91a84bf208 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -3,12 +3,6 @@ - - - - - - @@ -28,7 +22,6 @@