From fbc6d0de54031de840c39be3a2c7c63fe522c439 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:39:46 +0900 Subject: [PATCH] =?UTF-8?q?enhance:=20=E3=83=9A=E3=83=BC=E3=82=B8slug?= =?UTF-8?q?=E3=81=AB=E4=BD=BF=E7=94=A8=E5=8F=AF=E8=83=BD=E3=81=AA=E6=96=87?= =?UTF-8?q?=E5=AD=97=E3=82=92=E9=99=90=E5=AE=9A=20(#15395)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * paramã®æ£è¦è¡¨ç¾ã§å¼¾ãよã†ã« * apiWithDialogを使用ã™ã‚‹ã‚ˆã†ã« * Update CHANGELOG.md --------- Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> --- CHANGELOG.md | 2 +- locales/index.d.ts | 14 +-- locales/ja-JP.yml | 5 +- packages/backend/src/models/Page.ts | 2 + .../src/server/api/endpoints/pages/create.ts | 4 +- .../src/server/api/endpoints/pages/update.ts | 5 +- .../src/pages/page-editor/page-editor.vue | 111 ++++++++---------- 7 files changed, 59 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b9f91a38..7f48d1c532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,9 @@ - PlayãŒå®Ÿè£…ã•ã‚ŒãŸãŸã‚ã€ãƒšãƒ¼ã‚¸æ©Ÿèƒ½ã®ã€Œã‚½ãƒ¼ã‚¹ã‚’見るã€ã¯å‰Šé™¤ã•ã‚Œã¾ã—㟠### Server +- Enhance: ページã®URLã«ä½¿ç”¨å¯èƒ½ãªæ–‡å—ã‚’é™å®šã™ã‚‹ã‚ˆã†ã« - Fix: 個別ãŠçŸ¥ã‚‰ã›ãƒšãƒ¼ã‚¸ã®metaタグ出力ã®æ¡ä»¶ãŒé–“é•ã£ã¦ã„ãŸã®ã‚’ä¿®æ£ - ## 2025.1.0 ### Note diff --git a/locales/index.d.ts b/locales/index.d.ts index a0540fd228..4e26d5406b 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4195,7 +4195,7 @@ export interface Locale extends ILocale { */ "invalidParamError": string; /** - * リクエストパラメータã«å•é¡ŒãŒã‚ã‚Šã¾ã™ã€‚通常ã“ã‚Œã¯ãƒã‚°ã§ã™ãŒã€å…¥åŠ›ã—ãŸæ–‡å—æ•°ãŒå¤šã™ãŽã‚‹ç‰ã®å¯èƒ½æ€§ã‚‚ã‚ã‚Šã¾ã™ã€‚ + * リクエストパラメータã«å•é¡ŒãŒã‚ã‚Šã¾ã™ã€‚通常ã“ã‚Œã¯ãƒã‚°ã§ã™ãŒã€å…¥åŠ›ã—ãŸæ–‡å—æ•°ãŒå¤šã™ãŽã‚‹ãƒ»è¨±å¯ã•ã‚Œã¦ã„ãªã„æ–‡å—を入力ã—ã¦ã„ã‚‹ç‰ã®å¯èƒ½æ€§ã‚‚ã‚ã‚Šã¾ã™ã€‚ */ "invalidParamErrorDescription": string; /** @@ -9180,18 +9180,6 @@ export interface Locale extends ILocale { * ã‚½ãƒ¼ã‚¹ã‚’è¡¨ç¤ºä¸ */ "readPage": string; - /** - * ページを作æˆã—ã¾ã—㟠- */ - "created": string; - /** - * ページを更新ã—ã¾ã—㟠- */ - "updated": string; - /** - * ページを削除ã—ã¾ã—㟠- */ - "deleted": string; /** * ページè¨å®š */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a578704434..13d8aec9b8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1044,7 +1044,7 @@ youCannotCreateAnymore: "ã“れ以上作æˆã™ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。" cannotPerformTemporary: "一時的ã«åˆ©ç”¨ã§ãã¾ã›ã‚“" cannotPerformTemporaryDescription: "æ“作回数ãŒåˆ¶é™ã‚’超éŽã™ã‚‹ãŸã‚一時的ã«åˆ©ç”¨ã§ãã¾ã›ã‚“。ã—ã°ã‚‰ã時間を置ã„ã¦ã‹ã‚‰å†åº¦ãŠè©¦ã—ãã ã•ã„。" invalidParamError: "パラメータエラー" -invalidParamErrorDescription: "リクエストパラメータã«å•é¡ŒãŒã‚ã‚Šã¾ã™ã€‚通常ã“ã‚Œã¯ãƒã‚°ã§ã™ãŒã€å…¥åŠ›ã—ãŸæ–‡å—æ•°ãŒå¤šã™ãŽã‚‹ç‰ã®å¯èƒ½æ€§ã‚‚ã‚ã‚Šã¾ã™ã€‚" +invalidParamErrorDescription: "リクエストパラメータã«å•é¡ŒãŒã‚ã‚Šã¾ã™ã€‚通常ã“ã‚Œã¯ãƒã‚°ã§ã™ãŒã€å…¥åŠ›ã—ãŸæ–‡å—æ•°ãŒå¤šã™ãŽã‚‹ãƒ»è¨±å¯ã•ã‚Œã¦ã„ãªã„æ–‡å—を入力ã—ã¦ã„ã‚‹ç‰ã®å¯èƒ½æ€§ã‚‚ã‚ã‚Šã¾ã™ã€‚" permissionDeniedError: "æ“作ãŒæ‹’å¦ã•ã‚Œã¾ã—ãŸ" permissionDeniedErrorDescription: "ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ã¯ã“ã®æ“作を行ã†ãŸã‚ã®æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。" preset: "プリセット" @@ -2422,9 +2422,6 @@ _pages: newPage: "ページã®ä½œæˆ" editPage: "ページã®ç·¨é›†" readPage: "ソースを表示ä¸" - created: "ページを作æˆã—ã¾ã—ãŸ" - updated: "ページを更新ã—ã¾ã—ãŸ" - deleted: "ページを削除ã—ã¾ã—ãŸ" pageSetting: "ページè¨å®š" nameAlreadyExists: "指定ã•ã‚ŒãŸãƒšãƒ¼ã‚¸URLã¯æ—¢ã«å˜åœ¨ã—ã¦ã„ã¾ã™" invalidNameTitle: "ä¸æ£ãªãƒšãƒ¼ã‚¸URLã§ã™" diff --git a/packages/backend/src/models/Page.ts b/packages/backend/src/models/Page.ts index 1695bf570e..0b59e7a92c 100644 --- a/packages/backend/src/models/Page.ts +++ b/packages/backend/src/models/Page.ts @@ -118,3 +118,5 @@ export class MiPage { } } } + +export const pageNameSchema = { type: 'string', pattern: /^[^\s:\/?#\[\]@!$&'()*+,;=\\%\x00-\x20]{1,256}$/.source } as const; diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index fa03b0b457..6de5fe3d44 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -7,7 +7,7 @@ import ms from 'ms'; import { Inject, Injectable } from '@nestjs/common'; import type { DriveFilesRepository, PagesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; -import { MiPage } from '@/models/Page.js'; +import { MiPage, pageNameSchema } from '@/models/Page.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { PageEntityService } from '@/core/entities/PageEntityService.js'; import { DI } from '@/di-symbols.js'; @@ -51,7 +51,7 @@ export const paramDef = { type: 'object', properties: { title: { type: 'string' }, - name: { type: 'string', minLength: 1 }, + name: { ...pageNameSchema, minLength: 1 }, summary: { type: 'string', nullable: true }, content: { type: 'array', items: { type: 'object', additionalProperties: true, diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index e52d9c32df..a6aeb6002e 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -10,6 +10,7 @@ import type { PagesRepository, DriveFilesRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../error.js'; +import { pageNameSchema } from '@/models/Page.js'; export const meta = { tags: ['pages'], @@ -31,13 +32,11 @@ export const meta = { code: 'NO_SUCH_PAGE', id: '21149b9e-3616-4778-9592-c4ce89f5a864', }, - accessDenied: { message: 'Access denied.', code: 'ACCESS_DENIED', id: '3c15cd52-3b4b-4274-967d-6456fc4f792b', }, - noSuchFile: { message: 'No such file.', code: 'NO_SUCH_FILE', @@ -56,7 +55,7 @@ export const paramDef = { properties: { pageId: { type: 'string', format: 'misskey:id' }, title: { type: 'string' }, - name: { type: 'string', minLength: 1 }, + name: { ...pageNameSchema, minLength: 1 }, summary: { type: 'string', nullable: true }, content: { type: 'array', items: { type: 'object', additionalProperties: true, diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue index ddb808390c..c08cfebab3 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.vue @@ -96,7 +96,7 @@ const summary = ref<string | null>(null); const name = ref(Date.now().toString()); const eyeCatchingImage = ref<Misskey.entities.DriveFile | null>(null); const eyeCatchingImageId = ref<string | null>(null); -const font = ref('sans-serif'); +const font = ref<'sans-serif' | 'serif'>('sans-serif'); const content = ref<Misskey.entities.Page['content']>([]); const alignCenter = ref(false); const hideTitleWhenPinned = ref(false); @@ -113,7 +113,7 @@ watch(eyeCatchingImageId, async () => { } }); -function getSaveOptions() { +function getSaveOptions(): Misskey.entities.PagesCreateRequest { return { title: title.value.trim(), name: name.value.trim(), @@ -128,80 +128,69 @@ function getSaveOptions() { }; } -function save() { +async function save() { const options = getSaveOptions(); - const onError = err => { - if (err.id === '3d81ceae-475f-4600-b2a8-2bc116157532') { - if (err.info.param === 'name') { - os.alert({ - type: 'error', - title: i18n.ts._pages.invalidNameTitle, - text: i18n.ts._pages.invalidNameText, - }); - } - } else if (err.code === 'NAME_ALREADY_EXISTS') { - os.alert({ - type: 'error', + if (pageId.value) { + const updateOptions: Misskey.entities.PagesUpdateRequest = { + pageId: pageId.value, + ...options, + }; + + await os.apiWithDialog('pages/update', updateOptions, undefined, { + '2298a392-d4a1-44c5-9ebb-ac1aeaa5a9ab': { + title: i18n.ts.somethingHappened, text: i18n.ts._pages.nameAlreadyExists, - }); - } - }; + }, + }); - if (pageId.value) { - options.pageId = pageId.value; - misskeyApi('pages/update', options) - .then(page => { - currentName.value = name.value.trim(); - os.alert({ - type: 'success', - text: i18n.ts._pages.updated, - }); - }).catch(onError); + currentName.value = name.value.trim(); } else { - misskeyApi('pages/create', options) - .then(created => { - pageId.value = created.id; - currentName.value = name.value.trim(); - os.alert({ - type: 'success', - text: i18n.ts._pages.created, - }); - mainRouter.push(`/pages/edit/${pageId.value}`); - }).catch(onError); + const created = await os.apiWithDialog('pages/create', options, undefined, { + '4650348e-301c-499a-83c9-6aa988c66bc1': { + title: i18n.ts.somethingHappened, + text: i18n.ts._pages.nameAlreadyExists, + }, + }); + + pageId.value = created.id; + currentName.value = name.value.trim(); + mainRouter.replace(`/pages/edit/${pageId.value}`); } } -function del() { - os.confirm({ +async function del() { + if (!pageId.value) return; + + const { canceled } = await os.confirm({ type: 'warning', text: i18n.tsx.removeAreYouSure({ x: title.value.trim() }), - }).then(({ canceled }) => { - if (canceled) return; - misskeyApi('pages/delete', { - pageId: pageId.value, - }).then(() => { - os.alert({ - type: 'success', - text: i18n.ts._pages.deleted, - }); - mainRouter.push('/pages'); - }); }); + + if (canceled) return; + + await os.apiWithDialog('pages/delete', { + pageId: pageId.value, + }); + + mainRouter.replace('/pages'); } -function duplicate() { +async function duplicate() { title.value = title.value + ' - copy'; name.value = name.value + '-copy'; - misskeyApi('pages/create', getSaveOptions()).then(created => { - pageId.value = created.id; - currentName.value = name.value.trim(); - os.alert({ - type: 'success', - text: i18n.ts._pages.created, - }); - mainRouter.push(`/pages/edit/${pageId.value}`); + + const created = await os.apiWithDialog('pages/create', getSaveOptions(), undefined, { + '4650348e-301c-499a-83c9-6aa988c66bc1': { + title: i18n.ts.somethingHappened, + text: i18n.ts._pages.nameAlreadyExists, + }, }); + + pageId.value = created.id; + currentName.value = name.value.trim(); + + mainRouter.push(`/pages/edit/${pageId.value}`); } async function add() { @@ -216,7 +205,7 @@ async function add() { content.value.push({ id, type }); } -function setEyeCatchingImage(img) { +function setEyeCatchingImage(img: Event) { selectFile(img.currentTarget ?? img.target, null).then(file => { eyeCatchingImageId.value = file.id; }); -- GitLab