Skip to main content

max / makenotwork

4.9 KB · 122 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 function goBack() {
27 window.location.href = '/dashboard/project/' + projectSlug;
28 }
29
30 function saveBlogPost(publish) {
31 var f = getFields();
32 if (!f.title) {
33 postStatus.innerHTML = '<span style="color: var(--danger);">Title is required</span>';
34 return;
35 }
36 var payload = { title: f.title, body_markdown: f.body, is_published: publish };
37 if (f.slug) payload.slug = f.slug;
38
39 fetch('/api/projects/' + projectId + '/blog', {
40 method: 'POST',
41 headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
42 body: JSON.stringify(payload)
43 })
44 .then(function(res) {
45 if (!res.ok) return apiErrorMessage(res, 'Failed to create post').then(function(m) { throw new Error(m); });
46 return res.json();
47 })
48 .then(function() { goBack(); })
49 .catch(function(err) {
50 postStatus.style.color = 'var(--danger)';
51 postStatus.textContent = err.message;
52 });
53 }
54
55 function updateBlogPost(postId, publish) {
56 var f = getFields();
57 if (!f.title) {
58 postStatus.innerHTML = '<span style="color: var(--danger);">Title is required</span>';
59 return;
60 }
61 if (!f.slug) {
62 postStatus.innerHTML = '<span style="color: var(--danger);">Slug is required</span>';
63 return;
64 }
65 fetch('/api/blog/' + postId, {
66 method: 'PUT',
67 headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
68 body: JSON.stringify({ title: f.title, slug: f.slug, body_markdown: f.body, is_published: publish })
69 })
70 .then(function(res) {
71 if (!res.ok) return apiErrorMessage(res, 'Failed to update post').then(function(m) { throw new Error(m); });
72 return res.json();
73 })
74 .then(function() { goBack(); })
75 .catch(function(err) {
76 postStatus.style.color = 'var(--danger)';
77 postStatus.textContent = err.message;
78 });
79 }
80
81 document.getElementById('save-draft-btn').addEventListener('click', function() {
82 if (editingPostId) updateBlogPost(editingPostId, false);
83 else saveBlogPost(false);
84 });
85 document.getElementById('publish-btn').addEventListener('click', function() {
86 if (editingPostId) updateBlogPost(editingPostId, true);
87 else saveBlogPost(true);
88 });
89
90 // Auto-save for edit mode (30s debounce)
91 if (editingPostId) {
92 ['post-title', 'post-slug', 'post-body'].forEach(function(id) {
93 document.getElementById(id).addEventListener('input', function() {
94 clearTimeout(blogAutoSaveTimer);
95 blogAutoSaveTimer = setTimeout(function() {
96 var f = getFields();
97 if (!f.title || !f.slug) return;
98 postStatus.innerHTML = '<span style="opacity: 0.5;">Saving...</span>';
99 fetch('/api/blog/' + editingPostId, {
100 method: 'PUT',
101 headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
102 body: JSON.stringify({ title: f.title, slug: f.slug, body_markdown: f.body, is_published: false })
103 })
104 .then(function(res) {
105 if (!res.ok) return apiErrorMessage(res, 'Auto-save failed').then(function(m) { throw new Error(m); });
106 postStatus.innerHTML = '<span style="color: var(--text-muted);">Auto-saved</span>';
107 setTimeout(function() {
108 if (postStatus.textContent === 'Auto-saved') postStatus.innerHTML = '';
109 }, 3000);
110 })
111 .catch(function(err) {
112 var span = document.createElement('span');
113 span.style.color = 'var(--danger)';
114 span.textContent = err.message || 'Auto-save failed';
115 postStatus.replaceChildren(span);
116 });
117 }, 30000);
118 });
119 });
120 }
121 })();
122