diff --git a/graphql/post.ts b/graphql/post.ts index 9df64692..8554b6e5 100644 --- a/graphql/post.ts +++ b/graphql/post.ts @@ -5,6 +5,7 @@ import { renderMarkup } from "@hackerspub/models/markup"; import { createArticle, deleteArticleDraft, + updateArticle, updateArticleDraft, } from "@hackerspub/models/article"; import { createNote } from "@hackerspub/models/note"; @@ -359,6 +360,16 @@ export const ArticleContent = builder.drizzleNode("articleContentTable", { return renderCustomEmojis(html.html, content.source.post.emojis); }, }), + rawContent: t.field({ + type: "Markdown", + description: "The raw markdown content for editing.", + select: { + columns: { content: true }, + }, + resolve(content) { + return content.content; + }, + }), toc: t.field({ type: "JSON", description: "Table of contents for the article content.", @@ -1331,6 +1342,70 @@ builder.queryField("articleByYearAndSlug", (t) => }, })); +builder.relayMutationField( + "updateArticle", + { + inputFields: (t) => ({ + articleId: t.globalID({ for: [Article], required: true }), + title: t.string({ required: false }), + content: t.field({ type: "Markdown", required: false }), + tags: t.stringList({ required: false }), + language: t.field({ type: "Locale", required: false }), + allowLlmTranslation: t.boolean({ required: false }), + }), + }, + { + errors: { + types: [ + NotAuthenticatedError, + InvalidInputError, + ], + }, + async resolve(_root, args, ctx) { + const session = await ctx.session; + if (session == null) { + throw new NotAuthenticatedError(); + } + + const articleId = args.input.articleId.id; + // Find the post and its articleSource + const post = await ctx.db.query.postTable.findFirst({ + where: { id: articleId }, + with: { articleSource: true }, + }); + if (post == null || post.articleSource == null) { + throw new InvalidInputError("articleId"); + } + + // Verify ownership + if (post.articleSource.accountId !== session.accountId) { + throw new InvalidInputError("articleId"); + } + + const updated = await updateArticle(ctx.fedCtx, post.articleSource.id, { + title: args.input.title ?? undefined, + content: args.input.content ?? undefined, + tags: args.input.tags ?? undefined, + language: args.input.language?.baseName ?? undefined, + allowLlmTranslation: args.input.allowLlmTranslation ?? undefined, + }); + if (updated == null) { + throw new InvalidInputError("articleId"); + } + + return updated; + }, + }, + { + outputFields: (t) => ({ + article: t.field({ + type: Article, + resolve: (post) => post, + }), + }), + }, +); + interface UploadMediaResult { url: string; width: number; diff --git a/graphql/schema.graphql b/graphql/schema.graphql index 0bca36e1..460ffe83 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -321,6 +321,9 @@ type ArticleContent implements Node { language: Locale! originalLanguage: Locale published: DateTime! + + """The raw markdown content for editing.""" + rawContent: Markdown! summary: String summaryStarted: DateTime title: String! @@ -689,6 +692,7 @@ type Mutation { unregisterApnsDeviceToken(input: UnregisterApnsDeviceTokenInput!): UnregisterApnsDeviceTokenResult! unsharePost(input: UnsharePostInput!): UnsharePostResult! updateAccount(input: UpdateAccountInput!): UpdateAccountPayload! + updateArticle(input: UpdateArticleInput!): UpdateArticleResult! uploadMedia(input: UploadMediaInput!): UploadMediaResult! verifyPasskeyRegistration(accountId: ID!, name: String!, registrationResponse: JSON!): PasskeyRegistrationResult! } @@ -1441,6 +1445,23 @@ type UpdateAccountPayload { clientMutationId: ID } +input UpdateArticleInput { + allowLlmTranslation: Boolean + articleId: ID! + clientMutationId: ID + content: Markdown + language: Locale + tags: [String!] + title: String +} + +type UpdateArticlePayload { + article: Article! + clientMutationId: ID +} + +union UpdateArticleResult = InvalidInputError | NotAuthenticatedError | UpdateArticlePayload + input UploadMediaInput { clientMutationId: ID draftId: UUID diff --git a/web-next/src/locales/en-US/messages.po b/web-next/src/locales/en-US/messages.po index cdc0b330..c0fd52ed 100644 --- a/web-next/src/locales/en-US/messages.po +++ b/web-next/src/locales/en-US/messages.po @@ -128,11 +128,11 @@ msgstr "{0} shared {1}" msgid "{0} shared your post" msgstr "{0} shared your post" -#. placeholder {0}: article().actor.name +#. placeholder {0}: article().actor.rawName #. placeholder {0}: note().actor.name #. placeholder {1}: note().excerpt #. placeholder {1}: title() -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:153 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:167 #: src/routes/(root)/[handle]/[noteId].tsx:144 msgid "{0}: {1}" msgstr "{0}: {1}" @@ -193,6 +193,10 @@ msgstr "Add" msgid "Add {0}" msgstr "Add {0}" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:308 +msgid "Allow automatic translation by AI" +msgstr "Allow automatic translation by AI" + #: src/routes/(root)/sign/up/[token].tsx:296 msgid "An error occurred during signup. Please try again." msgstr "An error occurred during signup. Please try again." @@ -245,6 +249,10 @@ msgstr "Article Drafts" msgid "Article published" msgstr "Article published" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:182 +msgid "Article updated" +msgstr "Article updated" + #: src/components/article-composer/ArticleComposerPublishFields.tsx:24 msgid "article-url-slug" msgstr "article-url-slug" @@ -288,6 +296,7 @@ msgstr "Bold" #: src/components/PostActionMenu.tsx:137 #: src/components/RemoteFollowButton.tsx:217 #: src/components/RemoteFollowButton.tsx:279 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:325 #: src/routes/(root)/[handle]/settings/index.tsx:402 #: src/routes/(root)/[handle]/settings/passkeys.tsx:525 #: src/routes/(root)/authorize_interaction.tsx:190 @@ -321,7 +330,7 @@ msgid "Code of conduct" msgstr "Code of conduct" #. placeholder {0}: article().replies?.edges.length ?? 0 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:479 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:499 msgid "Comments ({0})" msgstr "Comments ({0})" @@ -332,6 +341,7 @@ msgstr "Compose" #: src/components/article-composer/ArticleComposerForm.tsx:53 #: src/components/NoteComposer.tsx:374 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:241 msgid "Content" msgstr "Content" @@ -470,10 +480,14 @@ msgstr "Drag to select the area you want to keep, then click “Crop” to updat msgid "e.g., @user@mastodon.social" msgstr "e.g., @user@mastodon.social" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:334 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:348 msgid "Edit" msgstr "Edit" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:223 +msgid "Edit Article" +msgstr "Edit Article" + #: src/routes/(root)/[handle]/drafts/[id].tsx:79 #: src/routes/(root)/[handle]/drafts/new.tsx:95 msgid "Edit Draft" @@ -520,6 +534,9 @@ msgstr "Enter your email or username below to sign in." #: src/components/NoteComposer.tsx:280 #: src/components/NoteComposer.tsx:288 #: src/components/NoteComposer.tsx:296 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:193 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:201 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:209 #: src/routes/(root)/[handle]/drafts/index.tsx:182 #: src/routes/(root)/[handle]/drafts/index.tsx:191 #: src/routes/(root)/[handle]/drafts/index.tsx:199 @@ -784,7 +801,7 @@ msgid "If enabled, the AI will generate a summary of the article for you. Otherw msgstr "If enabled, the AI will generate a summary of the article for you. Otherwise, the first few lines of the article will be used as the summary." #. placeholder {0}: "ACTIVITYPUB_URI" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:495 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:515 msgid "If you have a fediverse account, you can reply to this article from your own instance. Search {0} on your instance and reply to it." msgstr "If you have a fediverse account, you can reply to this article from your own instance. Search {0} on your instance and reply to it." @@ -809,6 +826,7 @@ msgstr "Invalid Fediverse handle format." #: src/components/article-composer/ArticleComposerContext.tsx:381 #: src/components/article-composer/ArticleComposerContext.tsx:446 #: src/components/NoteComposer.tsx:281 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:194 #: src/routes/(root)/[handle]/drafts/index.tsx:184 msgid "Invalid input: {0}" msgstr "Invalid input: {0}" @@ -865,6 +883,7 @@ msgstr "Joined on {0}" #: src/components/LanguageList.tsx:33 #: src/components/LanguageSelect.tsx:93 #: src/components/NoteComposer.tsx:421 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:291 msgid "Language" msgstr "Language" @@ -1008,6 +1027,7 @@ msgstr "Markdown guide" #: src/components/article-composer/ArticleComposerForm.tsx:99 #: src/components/NoteComposer.tsx:399 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:266 msgid "Markdown supported" msgstr "Markdown supported" @@ -1154,10 +1174,11 @@ msgstr "Or" msgid "Or enter the code from the email" msgstr "Or enter the code from the email" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:418 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:438 msgid "Other languages" msgstr "Other languages" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:102 #: src/routes/[...404].tsx:11 msgid "Page Not Found" msgstr "Page Not Found" @@ -1202,6 +1223,7 @@ msgid "Please correct the errors and try again." msgstr "Please correct the errors and try again." #: src/components/article-composer/ArticleComposerForm.tsx:44 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:232 msgid "Please enter a title for your article." msgstr "Please enter a title for your article." @@ -1378,6 +1400,10 @@ msgstr "Revoke passkey" msgid "Save" msgstr "Save" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 +msgid "Save Changes" +msgstr "Save Changes" + #: src/components/article-composer/ArticleComposerActions.tsx:32 msgid "Save Draft" msgstr "Save Draft" @@ -1387,6 +1413,7 @@ msgid "Save draft to see preview" msgstr "Save draft to see preview" #: src/components/article-composer/ArticleComposerActions.tsx:32 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 msgid "Saving..." msgstr "Saving..." @@ -1497,6 +1524,7 @@ msgstr "Something went wrong—please try again." #: src/components/article-composer/ArticleComposerContext.tsx:371 #: src/components/article-composer/ArticleComposerContext.tsx:436 #: src/components/NoteComposer.tsx:272 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:181 #: src/routes/(root)/[handle]/drafts/index.tsx:174 msgid "Success" msgstr "Success" @@ -1519,9 +1547,9 @@ msgid "Summarized by LLM" msgstr "Summarized by LLM" #: src/components/DocumentView.tsx:34 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:350 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:364 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:526 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:370 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:384 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:546 msgid "Table of contents" msgstr "Table of contents" @@ -1530,7 +1558,8 @@ msgstr "Table of contents" #~ msgstr "Tag" #: src/components/article-composer/ArticleComposerForm.tsx:142 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:536 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:280 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:556 msgid "Tags" msgstr "Tags" @@ -1538,6 +1567,10 @@ msgstr "Tags" msgid "Tell us about yourself…" msgstr "Tell us about yourself…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:105 +msgid "The article you're looking for doesn't exist or has been deleted." +msgstr "The article you're looking for doesn't exist or has been deleted." + #: src/routes/(root)/[handle]/settings/preferences.tsx:215 msgid "The default privacy setting for your notes." msgstr "The default privacy setting for your notes." @@ -1643,6 +1676,7 @@ msgid "Timeline" msgstr "Timeline" #: src/components/article-composer/ArticleComposerForm.tsx:40 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:228 msgid "Title" msgstr "Title" @@ -1661,15 +1695,16 @@ msgstr "Toggle sidebar" #. placeholder {0}: "LANGUAGE" #: src/components/ArticleCard.tsx:175 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:402 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:422 msgid "Translated from {0}" msgstr "Translated from {0}" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:277 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:291 msgid "Translating..." msgstr "Translating..." #: src/components/article-composer/ArticleComposerForm.tsx:146 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:284 msgid "Type tags separated by spaces" msgstr "Type tags separated by spaces" @@ -1787,10 +1822,14 @@ msgstr "What's on your mind?" msgid "Without shares" msgstr "Without shares" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:486 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:506 msgid "Write a reply..." msgstr "Write a reply..." +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:272 +msgid "Write your article here." +msgstr "Write your article here." + #: src/components/article-composer/ArticleComposerForm.tsx:113 msgid "Write your article here. You can use Markdown. Your article will be automatically saved as a draft while you're writing." msgstr "Write your article here. You can use Markdown. Your article will be automatically saved as a draft while you're writing." @@ -1848,6 +1887,10 @@ msgstr "You must be signed in to create a note" msgid "You must be signed in to delete a draft" msgstr "You must be signed in to delete a draft" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:202 +msgid "You must be signed in to edit an article" +msgstr "You must be signed in to edit an article" + #: src/components/article-composer/ArticleComposerContext.tsx:389 msgid "You must be signed in to publish an article" msgstr "You must be signed in to publish an article" diff --git a/web-next/src/locales/ja-JP/messages.po b/web-next/src/locales/ja-JP/messages.po index 436fd7e6..f07eb363 100644 --- a/web-next/src/locales/ja-JP/messages.po +++ b/web-next/src/locales/ja-JP/messages.po @@ -128,11 +128,11 @@ msgstr "{0}さんが{1}に共有" msgid "{0} shared your post" msgstr "{0}さんがあなたのコンテンツを共有しました" -#. placeholder {0}: article().actor.name +#. placeholder {0}: article().actor.rawName #. placeholder {0}: note().actor.name #. placeholder {1}: note().excerpt #. placeholder {1}: title() -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:153 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:167 #: src/routes/(root)/[handle]/[noteId].tsx:144 msgid "{0}: {1}" msgstr "{0}:{1}" @@ -193,6 +193,10 @@ msgstr "追加" msgid "Add {0}" msgstr "{0}を追加" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:308 +msgid "Allow automatic translation by AI" +msgstr "AIによる自動翻訳を許可" + #: src/routes/(root)/sign/up/[token].tsx:296 msgid "An error occurred during signup. Please try again." msgstr "登録中にエラーが発生しました。もう一度お試しください。" @@ -245,6 +249,10 @@ msgstr "記事の下書き" msgid "Article published" msgstr "記事を公開しました" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:182 +msgid "Article updated" +msgstr "記事を更新しました" + #: src/components/article-composer/ArticleComposerPublishFields.tsx:24 msgid "article-url-slug" msgstr "記事URLスラッグ" @@ -288,6 +296,7 @@ msgstr "太字" #: src/components/PostActionMenu.tsx:137 #: src/components/RemoteFollowButton.tsx:217 #: src/components/RemoteFollowButton.tsx:279 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:325 #: src/routes/(root)/[handle]/settings/index.tsx:402 #: src/routes/(root)/[handle]/settings/passkeys.tsx:525 #: src/routes/(root)/authorize_interaction.tsx:190 @@ -321,7 +330,7 @@ msgid "Code of conduct" msgstr "行動規範" #. placeholder {0}: article().replies?.edges.length ?? 0 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:479 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:499 msgid "Comments ({0})" msgstr "コメント({0})" @@ -332,6 +341,7 @@ msgstr "作成" #: src/components/article-composer/ArticleComposerForm.tsx:53 #: src/components/NoteComposer.tsx:374 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:241 msgid "Content" msgstr "内容" @@ -470,10 +480,14 @@ msgstr "保持したい領域をドラッグして選択し、「切り抜き」 msgid "e.g., @user@mastodon.social" msgstr "例: @user@mastodon.social" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:334 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:348 msgid "Edit" msgstr "編集" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:223 +msgid "Edit Article" +msgstr "記事を編集" + #: src/routes/(root)/[handle]/drafts/[id].tsx:79 #: src/routes/(root)/[handle]/drafts/new.tsx:95 msgid "Edit Draft" @@ -516,6 +530,9 @@ msgstr "以下にメールアドレスまたはユーザー名を入力してロ #: src/components/NoteComposer.tsx:280 #: src/components/NoteComposer.tsx:288 #: src/components/NoteComposer.tsx:296 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:193 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:201 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:209 #: src/routes/(root)/[handle]/drafts/index.tsx:182 #: src/routes/(root)/[handle]/drafts/index.tsx:191 #: src/routes/(root)/[handle]/drafts/index.tsx:199 @@ -780,7 +797,7 @@ msgid "If enabled, the AI will generate a summary of the article for you. Otherw msgstr "有効にすると、AIが記事の要約を生成します。無効の場合は、記事の最初の数行が要約として使用されます。" #. placeholder {0}: "ACTIVITYPUB_URI" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:495 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:515 msgid "If you have a fediverse account, you can reply to this article from your own instance. Search {0} on your instance and reply to it." msgstr "フェディバース(fediverse)アカウントをお持ちの場合、この記事に返信することができます。ご利用のインスタンスの検索バーに{0}を検索し、該当記事に返信してください。" @@ -805,6 +822,7 @@ msgstr "無効なフェディバースのハンドルの形式です。" #: src/components/article-composer/ArticleComposerContext.tsx:381 #: src/components/article-composer/ArticleComposerContext.tsx:446 #: src/components/NoteComposer.tsx:281 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:194 #: src/routes/(root)/[handle]/drafts/index.tsx:184 msgid "Invalid input: {0}" msgstr "無効な入力:{0}" @@ -861,6 +879,7 @@ msgstr "{0}に参加" #: src/components/LanguageList.tsx:33 #: src/components/LanguageSelect.tsx:93 #: src/components/NoteComposer.tsx:421 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:291 msgid "Language" msgstr "言語" @@ -1004,6 +1023,7 @@ msgstr "Markdown ガイド" #: src/components/article-composer/ArticleComposerForm.tsx:99 #: src/components/NoteComposer.tsx:399 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:266 msgid "Markdown supported" msgstr "Markdown対応" @@ -1149,10 +1169,11 @@ msgstr "または" msgid "Or enter the code from the email" msgstr "またはメールのコードを入力してください" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:418 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:438 msgid "Other languages" msgstr "他の言語" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:102 #: src/routes/[...404].tsx:11 msgid "Page Not Found" msgstr "ページが見つかりません" @@ -1197,6 +1218,7 @@ msgid "Please correct the errors and try again." msgstr "エラーを修正して、もう一度お試しください。" #: src/components/article-composer/ArticleComposerForm.tsx:44 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:232 msgid "Please enter a title for your article." msgstr "記事のタイトルを入力してください。" @@ -1373,6 +1395,10 @@ msgstr "パスキーを取り消す" msgid "Save" msgstr "保存" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 +msgid "Save Changes" +msgstr "変更を保存" + #: src/components/article-composer/ArticleComposerActions.tsx:32 msgid "Save Draft" msgstr "下書きを保存" @@ -1382,6 +1408,7 @@ msgid "Save draft to see preview" msgstr "下書きを保存するとプレビューが表示されます" #: src/components/article-composer/ArticleComposerActions.tsx:32 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 msgid "Saving..." msgstr "保存中..." @@ -1492,6 +1519,7 @@ msgstr "問題が発生しました。再度お試しください。" #: src/components/article-composer/ArticleComposerContext.tsx:371 #: src/components/article-composer/ArticleComposerContext.tsx:436 #: src/components/NoteComposer.tsx:272 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:181 #: src/routes/(root)/[handle]/drafts/index.tsx:174 msgid "Success" msgstr "成功" @@ -1514,9 +1542,9 @@ msgid "Summarized by LLM" msgstr "LLMによる要約" #: src/components/DocumentView.tsx:34 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:350 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:364 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:526 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:370 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:384 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:546 msgid "Table of contents" msgstr "目次" @@ -1525,7 +1553,8 @@ msgstr "目次" #~ msgstr "タグ" #: src/components/article-composer/ArticleComposerForm.tsx:142 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:536 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:280 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:556 msgid "Tags" msgstr "タグ" @@ -1533,6 +1562,10 @@ msgstr "タグ" msgid "Tell us about yourself…" msgstr "自己紹介をお聞かせください…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:105 +msgid "The article you're looking for doesn't exist or has been deleted." +msgstr "お探しの記事は存在しないか、削除されました。" + #: src/routes/(root)/[handle]/settings/preferences.tsx:215 msgid "The default privacy setting for your notes." msgstr "投稿のデフォルト公開設定です。" @@ -1638,6 +1671,7 @@ msgid "Timeline" msgstr "タイムライン" #: src/components/article-composer/ArticleComposerForm.tsx:40 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:228 msgid "Title" msgstr "タイトル" @@ -1656,15 +1690,16 @@ msgstr "サイドバーを切り替える" #. placeholder {0}: "LANGUAGE" #: src/components/ArticleCard.tsx:175 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:402 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:422 msgid "Translated from {0}" msgstr "{0}から翻訳" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:277 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:291 msgid "Translating..." msgstr "翻訳中…" #: src/components/article-composer/ArticleComposerForm.tsx:146 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:284 msgid "Type tags separated by spaces" msgstr "スペースで区切ってタグを入力" @@ -1782,10 +1817,14 @@ msgstr "今何してる?" msgid "Without shares" msgstr "共有除外" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:486 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:506 msgid "Write a reply..." msgstr "返信を書く…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:272 +msgid "Write your article here." +msgstr "ここに記事を書いてください。" + #: src/components/article-composer/ArticleComposerForm.tsx:113 msgid "Write your article here. You can use Markdown. Your article will be automatically saved as a draft while you're writing." msgstr "ここに記事を書いてください。Markdownが使えます。執筆中は自動的に下書きとして保存されます。" @@ -1843,6 +1882,10 @@ msgstr "投稿を作成するにはログインが必要です" msgid "You must be signed in to delete a draft" msgstr "下書きを削除するにはログインする必要があります" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:202 +msgid "You must be signed in to edit an article" +msgstr "記事を編集するにはログインする必要があります" + #: src/components/article-composer/ArticleComposerContext.tsx:389 msgid "You must be signed in to publish an article" msgstr "記事を公開するにはログインする必要があります" diff --git a/web-next/src/locales/ko-KR/messages.po b/web-next/src/locales/ko-KR/messages.po index 4dabe873..a3c59538 100644 --- a/web-next/src/locales/ko-KR/messages.po +++ b/web-next/src/locales/ko-KR/messages.po @@ -128,11 +128,11 @@ msgstr "{0} 님이 {1}에 공유함" msgid "{0} shared your post" msgstr "{0} 님이 회원님의 콘텐츠를 공유했습니다" -#. placeholder {0}: article().actor.name +#. placeholder {0}: article().actor.rawName #. placeholder {0}: note().actor.name #. placeholder {1}: note().excerpt #. placeholder {1}: title() -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:153 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:167 #: src/routes/(root)/[handle]/[noteId].tsx:144 msgid "{0}: {1}" msgstr "{0}: {1}" @@ -193,6 +193,10 @@ msgstr "추가" msgid "Add {0}" msgstr "{0} 추가" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:308 +msgid "Allow automatic translation by AI" +msgstr "AI 자동 번역 허용" + #: src/routes/(root)/sign/up/[token].tsx:296 msgid "An error occurred during signup. Please try again." msgstr "가입중에 오류가 발생했습니다. 다시 시도해 주시기 바랍니다." @@ -245,6 +249,10 @@ msgstr "게시글 임시 보관" msgid "Article published" msgstr "게시글을 공개했습니다" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:182 +msgid "Article updated" +msgstr "게시글이 수정되었습니다" + #: src/components/article-composer/ArticleComposerPublishFields.tsx:24 msgid "article-url-slug" msgstr "게시글-url-슬러그" @@ -288,6 +296,7 @@ msgstr "굵게" #: src/components/PostActionMenu.tsx:137 #: src/components/RemoteFollowButton.tsx:217 #: src/components/RemoteFollowButton.tsx:279 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:325 #: src/routes/(root)/[handle]/settings/index.tsx:402 #: src/routes/(root)/[handle]/settings/passkeys.tsx:525 #: src/routes/(root)/authorize_interaction.tsx:190 @@ -321,7 +330,7 @@ msgid "Code of conduct" msgstr "행동 강령" #. placeholder {0}: article().replies?.edges.length ?? 0 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:479 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:499 msgid "Comments ({0})" msgstr "댓글 ({0})" @@ -332,6 +341,7 @@ msgstr "작성" #: src/components/article-composer/ArticleComposerForm.tsx:53 #: src/components/NoteComposer.tsx:374 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:241 msgid "Content" msgstr "내용" @@ -470,10 +480,14 @@ msgstr "유지하려는 영역을 드래그하여 선택한 다음 “자르기 msgid "e.g., @user@mastodon.social" msgstr "예: @user@mastodon.social" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:334 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:348 msgid "Edit" msgstr "수정" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:223 +msgid "Edit Article" +msgstr "게시글 수정" + #: src/routes/(root)/[handle]/drafts/[id].tsx:79 #: src/routes/(root)/[handle]/drafts/new.tsx:95 msgid "Edit Draft" @@ -516,6 +530,9 @@ msgstr "로그인하려면 아래에 이메일 또는 아이디를 입력해주 #: src/components/NoteComposer.tsx:280 #: src/components/NoteComposer.tsx:288 #: src/components/NoteComposer.tsx:296 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:193 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:201 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:209 #: src/routes/(root)/[handle]/drafts/index.tsx:182 #: src/routes/(root)/[handle]/drafts/index.tsx:191 #: src/routes/(root)/[handle]/drafts/index.tsx:199 @@ -780,7 +797,7 @@ msgid "If enabled, the AI will generate a summary of the article for you. Otherw msgstr "활성화하면 AI가 글의 요약을 생성합니다. 비활성화 시 글의 처음 몇 줄이 요약으로 사용됩니다." #. placeholder {0}: "ACTIVITYPUB_URI" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:495 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:515 msgid "If you have a fediverse account, you can reply to this article from your own instance. Search {0} on your instance and reply to it." msgstr "연합우주(fediverse) 계정이 있으시다면, 이 게시글에 댓글을 달 수 있습니다. 사용하시는 인스턴스의 검색창에 {0}로 검색하신 뒤, 해당 게시글에 댓글을 남기시면 됩니다." @@ -805,6 +822,7 @@ msgstr "올바른 연합우주 핸들 형식이 아닙니다." #: src/components/article-composer/ArticleComposerContext.tsx:381 #: src/components/article-composer/ArticleComposerContext.tsx:446 #: src/components/NoteComposer.tsx:281 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:194 #: src/routes/(root)/[handle]/drafts/index.tsx:184 msgid "Invalid input: {0}" msgstr "잘못된 입력: {0}" @@ -861,6 +879,7 @@ msgstr "{0}에 가입" #: src/components/LanguageList.tsx:33 #: src/components/LanguageSelect.tsx:93 #: src/components/NoteComposer.tsx:421 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:291 msgid "Language" msgstr "언어" @@ -1004,6 +1023,7 @@ msgstr "Markdown 가이드" #: src/components/article-composer/ArticleComposerForm.tsx:99 #: src/components/NoteComposer.tsx:399 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:266 msgid "Markdown supported" msgstr "Markdown 사용 가능" @@ -1149,10 +1169,11 @@ msgstr "또는" msgid "Or enter the code from the email" msgstr "또는 이메일의 코드를 입력하세요" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:418 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:438 msgid "Other languages" msgstr "다른 언어" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:102 #: src/routes/[...404].tsx:11 msgid "Page Not Found" msgstr "페이지를 찾을 수 없습니다" @@ -1197,6 +1218,7 @@ msgid "Please correct the errors and try again." msgstr "오류를 수정하고 다시 시도해주세요." #: src/components/article-composer/ArticleComposerForm.tsx:44 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:232 msgid "Please enter a title for your article." msgstr "게시글 제목을 입력해주세요." @@ -1373,6 +1395,10 @@ msgstr "패스키를 취소" msgid "Save" msgstr "저장" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 +msgid "Save Changes" +msgstr "변경사항 저장" + #: src/components/article-composer/ArticleComposerActions.tsx:32 msgid "Save Draft" msgstr "임시 보관" @@ -1382,6 +1408,7 @@ msgid "Save draft to see preview" msgstr "미리보기를 보려면 임시 보관하세요" #: src/components/article-composer/ArticleComposerActions.tsx:32 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 msgid "Saving..." msgstr "저장하는 중..." @@ -1492,6 +1519,7 @@ msgstr "문제가 발생했습니다. 다시 시도해주세요." #: src/components/article-composer/ArticleComposerContext.tsx:371 #: src/components/article-composer/ArticleComposerContext.tsx:436 #: src/components/NoteComposer.tsx:272 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:181 #: src/routes/(root)/[handle]/drafts/index.tsx:174 msgid "Success" msgstr "성공" @@ -1514,9 +1542,9 @@ msgid "Summarized by LLM" msgstr "LLM 요약" #: src/components/DocumentView.tsx:34 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:350 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:364 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:526 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:370 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:384 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:546 msgid "Table of contents" msgstr "목차" @@ -1525,7 +1553,8 @@ msgstr "목차" #~ msgstr "태그" #: src/components/article-composer/ArticleComposerForm.tsx:142 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:536 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:280 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:556 msgid "Tags" msgstr "태그" @@ -1533,6 +1562,10 @@ msgstr "태그" msgid "Tell us about yourself…" msgstr "당신에 대해서 이야기 해주세요…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:105 +msgid "The article you're looking for doesn't exist or has been deleted." +msgstr "찾으시는 게시글이 존재하지 않거나 삭제되었습니다." + #: src/routes/(root)/[handle]/settings/preferences.tsx:215 msgid "The default privacy setting for your notes." msgstr "단문의 기본 공개 설정입니다." @@ -1638,6 +1671,7 @@ msgid "Timeline" msgstr "타임라인" #: src/components/article-composer/ArticleComposerForm.tsx:40 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:228 msgid "Title" msgstr "제목" @@ -1656,15 +1690,16 @@ msgstr "사이드바 전환" #. placeholder {0}: "LANGUAGE" #: src/components/ArticleCard.tsx:175 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:402 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:422 msgid "Translated from {0}" msgstr "{0}에서 번역됨" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:277 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:291 msgid "Translating..." msgstr "번역 중…" #: src/components/article-composer/ArticleComposerForm.tsx:146 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:284 msgid "Type tags separated by spaces" msgstr "공백으로 구분된 태그 입력" @@ -1782,10 +1817,14 @@ msgstr "무슨 생각 해요?" msgid "Without shares" msgstr "공유 제외" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:486 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:506 msgid "Write a reply..." msgstr "댓글을 입력하세요…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:272 +msgid "Write your article here." +msgstr "여기에 게시글을 작성하세요." + #: src/components/article-composer/ArticleComposerForm.tsx:113 msgid "Write your article here. You can use Markdown. Your article will be automatically saved as a draft while you're writing." msgstr "여기에 게시글을 작성하세요. Markdown을 사용할 수 있습니다. 작성하는 동안 자동으로 임시 보관됩니다." @@ -1843,6 +1882,10 @@ msgstr "단문을 작성하려면 로그인해야 합니다" msgid "You must be signed in to delete a draft" msgstr "임시 보관을 삭제하려면 로그인해야 합니다" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:202 +msgid "You must be signed in to edit an article" +msgstr "게시글을 수정하려면 로그인해야 합니다" + #: src/components/article-composer/ArticleComposerContext.tsx:389 msgid "You must be signed in to publish an article" msgstr "게시글을 공개하려면 로그인해야 합니다" diff --git a/web-next/src/locales/zh-CN/messages.po b/web-next/src/locales/zh-CN/messages.po index 334c3cc0..e97819b6 100644 --- a/web-next/src/locales/zh-CN/messages.po +++ b/web-next/src/locales/zh-CN/messages.po @@ -128,11 +128,11 @@ msgstr "{0}在{1}转帖了" msgid "{0} shared your post" msgstr "{0} 转发了你的内容" -#. placeholder {0}: article().actor.name +#. placeholder {0}: article().actor.rawName #. placeholder {0}: note().actor.name #. placeholder {1}: note().excerpt #. placeholder {1}: title() -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:153 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:167 #: src/routes/(root)/[handle]/[noteId].tsx:144 msgid "{0}: {1}" msgstr "{0}:{1}" @@ -193,6 +193,10 @@ msgstr "添加" msgid "Add {0}" msgstr "添加{0}" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:308 +msgid "Allow automatic translation by AI" +msgstr "允许 AI 自动翻译" + #: src/routes/(root)/sign/up/[token].tsx:296 msgid "An error occurred during signup. Please try again." msgstr "注册过程中发生错误。请重新尝试。" @@ -245,6 +249,10 @@ msgstr "文章草稿" msgid "Article published" msgstr "文章已发布" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:182 +msgid "Article updated" +msgstr "文章已更新" + #: src/components/article-composer/ArticleComposerPublishFields.tsx:24 msgid "article-url-slug" msgstr "文章网址别名" @@ -288,6 +296,7 @@ msgstr "粗体" #: src/components/PostActionMenu.tsx:137 #: src/components/RemoteFollowButton.tsx:217 #: src/components/RemoteFollowButton.tsx:279 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:325 #: src/routes/(root)/[handle]/settings/index.tsx:402 #: src/routes/(root)/[handle]/settings/passkeys.tsx:525 #: src/routes/(root)/authorize_interaction.tsx:190 @@ -321,7 +330,7 @@ msgid "Code of conduct" msgstr "行为准则" #. placeholder {0}: article().replies?.edges.length ?? 0 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:479 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:499 msgid "Comments ({0})" msgstr "评论({0})" @@ -332,6 +341,7 @@ msgstr "写作" #: src/components/article-composer/ArticleComposerForm.tsx:53 #: src/components/NoteComposer.tsx:374 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:241 msgid "Content" msgstr "内容" @@ -470,10 +480,14 @@ msgstr "拖动选择要保留的区域,然后点击「裁剪」来更新你的 msgid "e.g., @user@mastodon.social" msgstr "例如:@user@mastodon.social" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:334 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:348 msgid "Edit" msgstr "编辑" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:223 +msgid "Edit Article" +msgstr "编辑文章" + #: src/routes/(root)/[handle]/drafts/[id].tsx:79 #: src/routes/(root)/[handle]/drafts/new.tsx:95 msgid "Edit Draft" @@ -516,6 +530,9 @@ msgstr "请在下方输入您的邮箱或用户名以登录。" #: src/components/NoteComposer.tsx:280 #: src/components/NoteComposer.tsx:288 #: src/components/NoteComposer.tsx:296 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:193 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:201 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:209 #: src/routes/(root)/[handle]/drafts/index.tsx:182 #: src/routes/(root)/[handle]/drafts/index.tsx:191 #: src/routes/(root)/[handle]/drafts/index.tsx:199 @@ -780,7 +797,7 @@ msgid "If enabled, the AI will generate a summary of the article for you. Otherw msgstr "启用后,AI 将为您生成文章摘要。否则,将使用文章的前几行作为摘要。" #. placeholder {0}: "ACTIVITYPUB_URI" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:495 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:515 msgid "If you have a fediverse account, you can reply to this article from your own instance. Search {0} on your instance and reply to it." msgstr "如果你在联邦宇宙有个账户,你可以在你自己的实例里评论此文章。在你的实例搜索 {0} 后回复。" @@ -805,6 +822,7 @@ msgstr "联邦宇宙用户名格式无效。" #: src/components/article-composer/ArticleComposerContext.tsx:381 #: src/components/article-composer/ArticleComposerContext.tsx:446 #: src/components/NoteComposer.tsx:281 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:194 #: src/routes/(root)/[handle]/drafts/index.tsx:184 msgid "Invalid input: {0}" msgstr "无效输入:{0}" @@ -861,6 +879,7 @@ msgstr "于{0}加入" #: src/components/LanguageList.tsx:33 #: src/components/LanguageSelect.tsx:93 #: src/components/NoteComposer.tsx:421 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:291 msgid "Language" msgstr "语言" @@ -1004,6 +1023,7 @@ msgstr "Markdown 指南" #: src/components/article-composer/ArticleComposerForm.tsx:99 #: src/components/NoteComposer.tsx:399 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:266 msgid "Markdown supported" msgstr "Markdown 可用" @@ -1149,10 +1169,11 @@ msgstr "或" msgid "Or enter the code from the email" msgstr "或输入邮件中的验证码" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:418 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:438 msgid "Other languages" msgstr "其他语言" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:102 #: src/routes/[...404].tsx:11 msgid "Page Not Found" msgstr "页面未找到" @@ -1197,6 +1218,7 @@ msgid "Please correct the errors and try again." msgstr "请修正错误并重试。" #: src/components/article-composer/ArticleComposerForm.tsx:44 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:232 msgid "Please enter a title for your article." msgstr "请输入文章标题。" @@ -1373,6 +1395,10 @@ msgstr "撤销通行密钥" msgid "Save" msgstr "保存" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 +msgid "Save Changes" +msgstr "保存更改" + #: src/components/article-composer/ArticleComposerActions.tsx:32 msgid "Save Draft" msgstr "保存草稿" @@ -1382,6 +1408,7 @@ msgid "Save draft to see preview" msgstr "保存草稿以查看预览" #: src/components/article-composer/ArticleComposerActions.tsx:32 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 msgid "Saving..." msgstr "保存中..." @@ -1492,6 +1519,7 @@ msgstr "出现错误,请重试。" #: src/components/article-composer/ArticleComposerContext.tsx:371 #: src/components/article-composer/ArticleComposerContext.tsx:436 #: src/components/NoteComposer.tsx:272 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:181 #: src/routes/(root)/[handle]/drafts/index.tsx:174 msgid "Success" msgstr "成功" @@ -1514,9 +1542,9 @@ msgid "Summarized by LLM" msgstr "由 AI 生成的摘要" #: src/components/DocumentView.tsx:34 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:350 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:364 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:526 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:370 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:384 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:546 msgid "Table of contents" msgstr "目录" @@ -1525,7 +1553,8 @@ msgstr "目录" #~ msgstr "标签" #: src/components/article-composer/ArticleComposerForm.tsx:142 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:536 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:280 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:556 msgid "Tags" msgstr "标签" @@ -1533,6 +1562,10 @@ msgstr "标签" msgid "Tell us about yourself…" msgstr "请告诉我们关于您自己的信息…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:105 +msgid "The article you're looking for doesn't exist or has been deleted." +msgstr "您查找的文章不存在或已被删除。" + #: src/routes/(root)/[handle]/settings/preferences.tsx:215 msgid "The default privacy setting for your notes." msgstr "您帖子的默认隐私设置。" @@ -1638,6 +1671,7 @@ msgid "Timeline" msgstr "时间线" #: src/components/article-composer/ArticleComposerForm.tsx:40 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:228 msgid "Title" msgstr "标题" @@ -1656,15 +1690,16 @@ msgstr "切换侧边栏" #. placeholder {0}: "LANGUAGE" #: src/components/ArticleCard.tsx:175 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:402 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:422 msgid "Translated from {0}" msgstr "翻译自{0}" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:277 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:291 msgid "Translating..." msgstr "正在翻译…" #: src/components/article-composer/ArticleComposerForm.tsx:146 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:284 msgid "Type tags separated by spaces" msgstr "输入标签,用空格分隔" @@ -1782,10 +1817,14 @@ msgstr "你在想啥?" msgid "Without shares" msgstr "不含转帖" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:486 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:506 msgid "Write a reply..." msgstr "写个回复…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:272 +msgid "Write your article here." +msgstr "在此撰写您的文章。" + #: src/components/article-composer/ArticleComposerForm.tsx:113 msgid "Write your article here. You can use Markdown. Your article will be automatically saved as a draft while you're writing." msgstr "在此撰写文章。您可以使用 Markdown。在您撰写时,文章将自动保存为草稿。" @@ -1843,6 +1882,10 @@ msgstr "你必须登录才能创建帖子" msgid "You must be signed in to delete a draft" msgstr "您必须登录才能删除草稿" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:202 +msgid "You must be signed in to edit an article" +msgstr "您必须登录才能编辑文章" + #: src/components/article-composer/ArticleComposerContext.tsx:389 msgid "You must be signed in to publish an article" msgstr "您必须登录才能发布文章" diff --git a/web-next/src/locales/zh-TW/messages.po b/web-next/src/locales/zh-TW/messages.po index 8877d676..21fa325e 100644 --- a/web-next/src/locales/zh-TW/messages.po +++ b/web-next/src/locales/zh-TW/messages.po @@ -128,11 +128,11 @@ msgstr "{0}在{1}轉貼了" msgid "{0} shared your post" msgstr "{0} 轉貼了你的內容" -#. placeholder {0}: article().actor.name +#. placeholder {0}: article().actor.rawName #. placeholder {0}: note().actor.name #. placeholder {1}: note().excerpt #. placeholder {1}: title() -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:153 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:167 #: src/routes/(root)/[handle]/[noteId].tsx:144 msgid "{0}: {1}" msgstr "{0}:{1}" @@ -193,6 +193,10 @@ msgstr "新增" msgid "Add {0}" msgstr "新增{0}" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:308 +msgid "Allow automatic translation by AI" +msgstr "允許 AI 自動翻譯" + #: src/routes/(root)/sign/up/[token].tsx:296 msgid "An error occurred during signup. Please try again." msgstr "註冊過程中發生錯誤。請重試。" @@ -245,6 +249,10 @@ msgstr "文章草稿" msgid "Article published" msgstr "文章已發布" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:182 +msgid "Article updated" +msgstr "文章已更新" + #: src/components/article-composer/ArticleComposerPublishFields.tsx:24 msgid "article-url-slug" msgstr "文章網址別名" @@ -288,6 +296,7 @@ msgstr "粗體" #: src/components/PostActionMenu.tsx:137 #: src/components/RemoteFollowButton.tsx:217 #: src/components/RemoteFollowButton.tsx:279 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:325 #: src/routes/(root)/[handle]/settings/index.tsx:402 #: src/routes/(root)/[handle]/settings/passkeys.tsx:525 #: src/routes/(root)/authorize_interaction.tsx:190 @@ -321,7 +330,7 @@ msgid "Code of conduct" msgstr "行為準則" #. placeholder {0}: article().replies?.edges.length ?? 0 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:479 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:499 msgid "Comments ({0})" msgstr "評論({0})" @@ -332,6 +341,7 @@ msgstr "寫作" #: src/components/article-composer/ArticleComposerForm.tsx:53 #: src/components/NoteComposer.tsx:374 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:241 msgid "Content" msgstr "內容" @@ -470,10 +480,14 @@ msgstr "拖動選擇要保留的區域,然後點擊「裁剪」來更新你的 msgid "e.g., @user@mastodon.social" msgstr "例如:@user@mastodon.social" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:334 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:348 msgid "Edit" msgstr "編輯" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:223 +msgid "Edit Article" +msgstr "編輯文章" + #: src/routes/(root)/[handle]/drafts/[id].tsx:79 #: src/routes/(root)/[handle]/drafts/new.tsx:95 msgid "Edit Draft" @@ -516,6 +530,9 @@ msgstr "請在下方輸入您的電子郵件或使用者名稱以登入。" #: src/components/NoteComposer.tsx:280 #: src/components/NoteComposer.tsx:288 #: src/components/NoteComposer.tsx:296 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:193 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:201 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:209 #: src/routes/(root)/[handle]/drafts/index.tsx:182 #: src/routes/(root)/[handle]/drafts/index.tsx:191 #: src/routes/(root)/[handle]/drafts/index.tsx:199 @@ -780,7 +797,7 @@ msgid "If enabled, the AI will generate a summary of the article for you. Otherw msgstr "啟用後,AI 將為您生成文章摘要。否則,將使用文章的前幾行作為摘要。" #. placeholder {0}: "ACTIVITYPUB_URI" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:495 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:515 msgid "If you have a fediverse account, you can reply to this article from your own instance. Search {0} on your instance and reply to it." msgstr "如果你在聯邦宇宙有個帳戶,你可以在你自己的站台裡評論此文章。在你的站台搜尋 {0} 後回覆。" @@ -805,6 +822,7 @@ msgstr "聯邦宇宙使用者名稱格式無效。" #: src/components/article-composer/ArticleComposerContext.tsx:381 #: src/components/article-composer/ArticleComposerContext.tsx:446 #: src/components/NoteComposer.tsx:281 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:194 #: src/routes/(root)/[handle]/drafts/index.tsx:184 msgid "Invalid input: {0}" msgstr "無效輸入:{0}" @@ -861,6 +879,7 @@ msgstr "於{0}加入" #: src/components/LanguageList.tsx:33 #: src/components/LanguageSelect.tsx:93 #: src/components/NoteComposer.tsx:421 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:291 msgid "Language" msgstr "語言" @@ -1004,6 +1023,7 @@ msgstr "Markdown 指南" #: src/components/article-composer/ArticleComposerForm.tsx:99 #: src/components/NoteComposer.tsx:399 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:266 msgid "Markdown supported" msgstr "Markdown 可用" @@ -1149,10 +1169,11 @@ msgstr "或" msgid "Or enter the code from the email" msgstr "或輸入郵件中的驗證碼" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:418 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:438 msgid "Other languages" msgstr "其他語言" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:102 #: src/routes/[...404].tsx:11 msgid "Page Not Found" msgstr "頁面未找到" @@ -1197,6 +1218,7 @@ msgid "Please correct the errors and try again." msgstr "請修正錯誤並重試。" #: src/components/article-composer/ArticleComposerForm.tsx:44 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:232 msgid "Please enter a title for your article." msgstr "請輸入文章標題。" @@ -1373,6 +1395,10 @@ msgstr "撤銷通行金鑰" msgid "Save" msgstr "儲存" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 +msgid "Save Changes" +msgstr "儲存變更" + #: src/components/article-composer/ArticleComposerActions.tsx:32 msgid "Save Draft" msgstr "儲存草稿" @@ -1382,6 +1408,7 @@ msgid "Save draft to see preview" msgstr "儲存草稿以查看預覽" #: src/components/article-composer/ArticleComposerActions.tsx:32 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:331 msgid "Saving..." msgstr "儲存中..." @@ -1492,6 +1519,7 @@ msgstr "發生錯誤,請重試。" #: src/components/article-composer/ArticleComposerContext.tsx:371 #: src/components/article-composer/ArticleComposerContext.tsx:436 #: src/components/NoteComposer.tsx:272 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:181 #: src/routes/(root)/[handle]/drafts/index.tsx:174 msgid "Success" msgstr "成功" @@ -1514,9 +1542,9 @@ msgid "Summarized by LLM" msgstr "由 AI 生成的摘要" #: src/components/DocumentView.tsx:34 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:350 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:364 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:526 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:370 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:384 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:546 msgid "Table of contents" msgstr "目錄" @@ -1525,7 +1553,8 @@ msgstr "目錄" #~ msgstr "標籤" #: src/components/article-composer/ArticleComposerForm.tsx:142 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:536 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:280 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:556 msgid "Tags" msgstr "標籤" @@ -1533,6 +1562,10 @@ msgstr "標籤" msgid "Tell us about yourself…" msgstr "介紹您自己…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:105 +msgid "The article you're looking for doesn't exist or has been deleted." +msgstr "您查找的文章不存在或已被刪除。" + #: src/routes/(root)/[handle]/settings/preferences.tsx:215 msgid "The default privacy setting for your notes." msgstr "您貼文的預設隱私設定。" @@ -1638,6 +1671,7 @@ msgid "Timeline" msgstr "時間軸" #: src/components/article-composer/ArticleComposerForm.tsx:40 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:228 msgid "Title" msgstr "標題" @@ -1656,15 +1690,16 @@ msgstr "切換側邊欄" #. placeholder {0}: "LANGUAGE" #: src/components/ArticleCard.tsx:175 -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:402 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:422 msgid "Translated from {0}" msgstr "翻譯自{0}" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:277 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:291 msgid "Translating..." msgstr "正在翻譯…" #: src/components/article-composer/ArticleComposerForm.tsx:146 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:284 msgid "Type tags separated by spaces" msgstr "輸入標籤,用空格分隔" @@ -1782,10 +1817,14 @@ msgstr "你在想什麼?" msgid "Without shares" msgstr "不含轉貼" -#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:486 +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx:506 msgid "Write a reply..." msgstr "寫個回覆…" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:272 +msgid "Write your article here." +msgstr "在此撰寫您的文章。" + #: src/components/article-composer/ArticleComposerForm.tsx:113 msgid "Write your article here. You can use Markdown. Your article will be automatically saved as a draft while you're writing." msgstr "在此撰寫文章。您可以使用 Markdown。在您撰寫時,文章將自動儲存為草稿。" @@ -1843,6 +1882,10 @@ msgstr "你必須登入才能建立貼文" msgid "You must be signed in to delete a draft" msgstr "您必須登入才能刪除草稿" +#: src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx:202 +msgid "You must be signed in to edit an article" +msgstr "您必須登入才能編輯文章" + #: src/components/article-composer/ArticleComposerContext.tsx:389 msgid "You must be signed in to publish an article" msgstr "您必須登入才能發布文章" diff --git a/web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx b/web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx new file mode 100644 index 00000000..23554056 --- /dev/null +++ b/web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/edit.tsx @@ -0,0 +1,338 @@ +import { + query, + type RouteDefinition, + useNavigate, + useParams, +} from "@solidjs/router"; +import { HttpStatusCode } from "@solidjs/start"; +import { graphql } from "relay-runtime"; +import { createSignal, Show } from "solid-js"; +import { + createFragment, + createMutation, + createPreloadedQuery, + loadQuery, + useRelayEnvironment, +} from "solid-relay"; +import { LanguageSelect } from "~/components/LanguageSelect.tsx"; +import { TagInput } from "~/components/TagInput.tsx"; +import { Button } from "~/components/ui/button.tsx"; +import { MarkdownEditor } from "~/components/ui/markdown-editor.tsx"; +import { + TextField, + TextFieldInput, + TextFieldLabel, +} from "~/components/ui/text-field.tsx"; +import { showToast } from "~/components/ui/toast.tsx"; +import { useLingui } from "~/lib/i18n/macro.d.ts"; +import type { editPageQuery } from "./__generated__/editPageQuery.graphql.ts"; +import type { edit_article$key } from "./__generated__/edit_article.graphql.ts"; +import type { edit_updateArticle_Mutation } from "./__generated__/edit_updateArticle_Mutation.graphql.ts"; + +export const route = { + matchFilters: { + handle: /^@/, + }, + preload(args) { + const handle = args.params.handle!; + const idOrYear = args.params.idOrYear!; + const slug = args.params.slug!; + void loadPageQuery(handle, idOrYear, slug); + }, +} satisfies RouteDefinition; + +const editPageQueryDef = graphql` + query editPageQuery( + $handle: String! + $idOrYear: String! + $slug: String! + ) { + articleByYearAndSlug( + handle: $handle + idOrYear: $idOrYear + slug: $slug + ) { + ...edit_article + } + } +`; + +const loadPageQuery = query( + (handle: string, idOrYear: string, slug: string) => + loadQuery( + useRelayEnvironment()(), + editPageQueryDef, + { handle, idOrYear, slug }, + ), + "loadArticleEditPageQuery", +); + +export default function ArticleEditPage() { + const params = useParams(); + const handle = params.handle!; + const idOrYear = params.idOrYear!; + const slug = params.slug!; + + const data = createPreloadedQuery( + editPageQueryDef, + () => loadPageQuery(handle, idOrYear, slug), + ); + + return ( + + {(data) => ( + } + > + {(article) => } + + )} + + ); +} + +interface ArticleEditFormProps { + $article: edit_article$key; +} + +const updateArticleMutation = graphql` + mutation edit_updateArticle_Mutation($input: UpdateArticleInput!) { + updateArticle(input: $input) { + __typename + ... on UpdateArticlePayload { + article { + id + url + } + } + ... on InvalidInputError { + inputPath + } + ... on NotAuthenticatedError { + notAuthenticated + } + } + } +`; + +function ArticleEditForm(props: ArticleEditFormProps) { + const { t } = useLingui(); + const navigate = useNavigate(); + + const article = createFragment( + graphql` + fragment edit_article on Article { + id + actor { + isViewer + username + } + contents { + title + rawContent + language + } + tags + allowLlmTranslation + publishedYear + slug + } + `, + () => props.$article, + ); + + const [commitUpdate, isUpdating] = createMutation< + edit_updateArticle_Mutation + >(updateArticleMutation); + + // Initialize form state from article data + const content = () => article()?.contents?.[0]; + const [title, setTitle] = createSignal(content()?.title ?? ""); + const [markdown, setMarkdown] = createSignal(content()?.rawContent ?? ""); + const [tags, setTags] = createSignal(article()?.tags ?? []); + const [language, setLanguage] = createSignal( + content()?.language ? new Intl.Locale(content()!.language) : undefined, + ); + const [allowLlmTranslation, setAllowLlmTranslation] = createSignal( + article()?.allowLlmTranslation ?? false, + ); + + const handleSave = () => { + const a = article(); + if (!a) return; + + commitUpdate({ + variables: { + input: { + articleId: a.id, + title: title(), + content: markdown(), + tags: tags(), + language: language()?.baseName, + allowLlmTranslation: allowLlmTranslation(), + }, + }, + onCompleted(response) { + if ( + response.updateArticle.__typename === "UpdateArticlePayload" + ) { + showToast({ + title: t`Success`, + description: t`Article updated`, + variant: "success", + }); + const articleUrl = response.updateArticle.article.url; + if (articleUrl) { + navigate(new URL(articleUrl).pathname); + } + } else if ( + response.updateArticle.__typename === "InvalidInputError" + ) { + showToast({ + title: t`Error`, + description: t`Invalid input: ${response.updateArticle.inputPath}`, + variant: "error", + }); + } else if ( + response.updateArticle.__typename === "NotAuthenticatedError" + ) { + showToast({ + title: t`Error`, + description: t`You must be signed in to edit an article`, + variant: "error", + }); + } + }, + onError(error) { + showToast({ + title: t`Error`, + description: error.message, + variant: "error", + }); + }, + }); + }; + + return ( + } + > +
+

{t`Edit Article`}

+ +
+ {/* Title */} + + {t`Title`} + setTitle(e.currentTarget.value)} + placeholder={t`Please enter a title for your article.`} + required + class="text-2xl font-bold" + /> + + + {/* Content */} + + + {/* Tags */} +
+ + +
+ + {/* Language */} +
+ + +
+ + {/* Allow LLM Translation */} + + + {/* Actions */} +
+ + +
+
+
+
+ ); +} diff --git a/web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx b/web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx index 37aad606..dd44de7e 100644 --- a/web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx +++ b/web-next/src/routes/(root)/[handle]/[idOrYear]/[slug]/index.tsx @@ -78,6 +78,7 @@ const loadPageQuery = query( ); export default function ArticlePage() { + const { t } = useLingui(); const params = useParams(); const handle = params.handle!; const idOrYear = params.idOrYear!; @@ -93,7 +94,19 @@ export default function ArticlePage() { {(data) => ( } + fallback={ + <> + +
+

+ {t`Page Not Found`} +

+

+ {t`The article you're looking for doesn't exist or has been deleted.`} +

+
+ + } > {(article) => ( <> @@ -335,7 +348,13 @@ function ArticleBody(props: ArticleBodyProps) { {t`Edit`} · - + + navigate( + `/@${article().actor.username}`, + )} + />