Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions graphql/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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.",
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 21 additions & 0 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down Expand Up @@ -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!
}
Expand Down Expand Up @@ -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
Expand Down
69 changes: 56 additions & 13 deletions web-next/src/locales/en-US/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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})"

Expand All @@ -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"

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."

Expand All @@ -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}"
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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."

Expand Down Expand Up @@ -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"
Expand All @@ -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..."

Expand Down Expand Up @@ -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"
Expand All @@ -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"

Expand All @@ -1530,14 +1558,19 @@ 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"

#: src/routes/(root)/sign/up/[token].tsx:414
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."
Expand Down Expand Up @@ -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"

Expand All @@ -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"

Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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"
Expand Down
Loading
Loading