Skip to main content

max / makenotwork

5.2 KB · 128 lines History Blame Raw
1 /**
2 * Blog post editor: save draft, publish, auto-save.
3 *
4 * Full page (not HTMX partial), no re-init needed.
5 * Reads project/post data from data attributes on #blog-editor.
6 * Depends on: mnw.js (csrfHeaders).
7 */
8 (function() {
9 var editor = document.getElementById('blog-editor');
10 if (!editor) return;
11
12 var projectId = editor.dataset.projectId;
13 var projectSlug = editor.dataset.projectSlug;
14 var editingPostId = editor.dataset.postId || null;
15 var blogAutoSaveTimer = null;
16 var postStatus = document.getElementById('post-status');
17
18 function getFields() {
19 return {
20 title: document.getElementById('post-title').value.trim(),
21 slug: document.getElementById('post-slug').value.trim(),
22 body: document.getElementById('post-body').value
23 };
24 }
25
26 // Present only on the changelog project editor; null elsewhere.
27 var landingToggle = document.getElementById('post-show-on-landing');
28
29 function goBack() {
30 window.location.href = '/dashboard/project/' + projectSlug;
31 }
32
33 function saveBlogPost(publish) {
34 var f = getFields();
35 if (!f.title) {
36 postStatus.innerHTML = '<span style="color: var(--danger);">Title is required</span>';
37 return;
38 }
39 var payload = { title: f.title, body_markdown: f.body, is_published: publish };
40 if (f.slug) payload.slug = f.slug;
41 if (landingToggle) payload.show_on_landing = landingToggle.checked;
42
43 fetch('/api/projects/' + projectId + '/blog', {
44 method: 'POST',
45 headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
46 body: JSON.stringify(payload)
47 })
48 .then(function(res) {
49 if (!res.ok) return apiErrorMessage(res, 'Failed to create post').then(function(m) { throw new Error(m); });
50 return res.json();
51 })
52 .then(function() { goBack(); })
53 .catch(function(err) {
54 postStatus.style.color = 'var(--danger)';
55 postStatus.textContent = err.message;
56 });
57 }
58
59 function updateBlogPost(postId, publish) {
60 var f = getFields();
61 if (!f.title) {
62 postStatus.innerHTML = '<span style="color: var(--danger);">Title is required</span>';
63 return;
64 }
65 if (!f.slug) {
66 postStatus.innerHTML = '<span style="color: var(--danger);">Slug is required</span>';
67 return;
68 }
69 var updatePayload = { title: f.title, slug: f.slug, body_markdown: f.body, is_published: publish };
70 if (landingToggle) updatePayload.show_on_landing = landingToggle.checked;
71 fetch('/api/blog/' + postId, {
72 method: 'PUT',
73 headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
74 body: JSON.stringify(updatePayload)
75 })
76 .then(function(res) {
77 if (!res.ok) return apiErrorMessage(res, 'Failed to update post').then(function(m) { throw new Error(m); });
78 return res.json();
79 })
80 .then(function() { goBack(); })
81 .catch(function(err) {
82 postStatus.style.color = 'var(--danger)';
83 postStatus.textContent = err.message;
84 });
85 }
86
87 document.getElementById('save-draft-btn').addEventListener('click', function() {
88 if (editingPostId) updateBlogPost(editingPostId, false);
89 else saveBlogPost(false);
90 });
91 document.getElementById('publish-btn').addEventListener('click', function() {
92 if (editingPostId) updateBlogPost(editingPostId, true);
93 else saveBlogPost(true);
94 });
95
96 // Auto-save for edit mode (30s debounce)
97 if (editingPostId) {
98 ['post-title', 'post-slug', 'post-body'].forEach(function(id) {
99 document.getElementById(id).addEventListener('input', function() {
100 clearTimeout(blogAutoSaveTimer);
101 blogAutoSaveTimer = setTimeout(function() {
102 var f = getFields();
103 if (!f.title || !f.slug) return;
104 postStatus.innerHTML = '<span style="opacity: 0.5;">Saving...</span>';
105 fetch('/api/blog/' + editingPostId, {
106 method: 'PUT',
107 headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
108 body: JSON.stringify({ title: f.title, slug: f.slug, body_markdown: f.body, is_published: false })
109 })
110 .then(function(res) {
111 if (!res.ok) return apiErrorMessage(res, 'Auto-save failed').then(function(m) { throw new Error(m); });
112 postStatus.innerHTML = '<span style="color: var(--text-muted);">Auto-saved</span>';
113 setTimeout(function() {
114 if (postStatus.textContent === 'Auto-saved') postStatus.innerHTML = '';
115 }, 3000);
116 })
117 .catch(function(err) {
118 var span = document.createElement('span');
119 span.style.color = 'var(--danger)';
120 span.textContent = err.message || 'Auto-save failed';
121 postStatus.replaceChildren(span);
122 });
123 }, 30000);
124 });
125 });
126 }
127 })();
128