feat: stage edits to published articles as drafts (#14842)
This commit is contained in:
+53
-2
@@ -4,8 +4,12 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { PORTALS_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import { buildPortalArticleURL } from 'dashboard/helper/portalHelper';
|
||||
import {
|
||||
buildPortalArticleURL,
|
||||
ARTICLE_STATUSES,
|
||||
} from 'dashboard/helper/portalHelper';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { rendersIdentically } from 'dashboard/helper/articleDiffHelper';
|
||||
|
||||
import ArticleEditor from 'dashboard/components-next/HelpCenter/Pages/ArticleEditorPage/ArticleEditor.vue';
|
||||
|
||||
@@ -40,13 +44,60 @@ const articleLink = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
// On a published article, title/content edits stage into draft_* columns (kept
|
||||
// off the live site). Anywhere else they save straight to the live record — and
|
||||
// we drop any leftover draft (e.g. left behind when the card/bulk menu moved a
|
||||
// published article to draft) so a later publish can't resurrect stale content.
|
||||
const stageDraftFields = values => {
|
||||
if (article.value?.status !== ARTICLE_STATUSES.PUBLISHED) {
|
||||
const hasStaleDraft =
|
||||
article.value?.draftTitle != null || article.value?.draftContent != null;
|
||||
if (!hasStaleDraft) return values;
|
||||
// The editor is showing the staged draft, so promote both fields to the live
|
||||
// record (the field being autosaved wins) before dropping the drafts —
|
||||
// otherwise saving one field would snap the other back to the old live value.
|
||||
return {
|
||||
...values,
|
||||
title: values.title ?? article.value.draftTitle ?? article.value.title,
|
||||
content:
|
||||
values.content ?? article.value.draftContent ?? article.value.content,
|
||||
draft_title: null,
|
||||
draft_content: null,
|
||||
};
|
||||
}
|
||||
|
||||
const staged = { ...values };
|
||||
['title', 'content'].forEach(field => {
|
||||
if (field in staged) {
|
||||
staged[`draft_${field}`] = staged[field];
|
||||
delete staged[field];
|
||||
}
|
||||
});
|
||||
|
||||
// Clear the draft when it matches the live version (a revert, or a body edit
|
||||
// the renderer ignores like a blank line) so it doesn't leave a "pending
|
||||
// changes" badge with nothing to compare. The title is shown as raw escaped
|
||||
// text, so compare it exactly; only the body is Markdown, so compare its render.
|
||||
const liveTitle = article.value.title ?? '';
|
||||
const liveContent = article.value.content ?? '';
|
||||
const nextTitle = staged.draft_title ?? article.value.draftTitle ?? liveTitle;
|
||||
const nextContent =
|
||||
staged.draft_content ?? article.value.draftContent ?? liveContent;
|
||||
if (nextTitle === liveTitle && rendersIdentically(liveContent, nextContent)) {
|
||||
staged.draft_title = null;
|
||||
staged.draft_content = null;
|
||||
}
|
||||
|
||||
return staged;
|
||||
};
|
||||
|
||||
const saveArticle = async ({ ...values }) => {
|
||||
isUpdating.value = true;
|
||||
try {
|
||||
await store.dispatch('articles/update', {
|
||||
portalSlug,
|
||||
articleId: articleSlug,
|
||||
...values,
|
||||
...stageDraftFields(values),
|
||||
});
|
||||
isSaved.value = true;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user