@gpdoc/cli 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/editor-preview.js +49 -5
- package/package.json +1 -1
- package/web/editor.html +61 -59
package/lib/editor-preview.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createServer } from 'node:http';
|
|
|
3
3
|
import { readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
const {
|
|
6
|
+
createManagedDocument,
|
|
6
7
|
readDocument,
|
|
7
8
|
serializeManagedMarkdown,
|
|
8
9
|
validateDocument,
|
|
@@ -102,11 +103,44 @@ function contentForSave(snapshot, content) {
|
|
|
102
103
|
return content;
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
function isPlainObject(value) {
|
|
107
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
108
|
+
&& (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// @spec CLI-040, CLI-042, CLI-043, CLI-044
|
|
112
|
+
function metadataForSave(snapshot, metadata) {
|
|
113
|
+
if (!isPlainObject(metadata)) {
|
|
114
|
+
throw new EditorPreviewError('INVALID_INPUT', 'Front matter metadata must be an object.');
|
|
115
|
+
}
|
|
116
|
+
const currentMetadata = snapshot.document.managed
|
|
117
|
+
? snapshot.document.metadata
|
|
118
|
+
: createManagedDocument(snapshot.document.title, snapshot.document.body);
|
|
119
|
+
const nextMetadata = {
|
|
120
|
+
...currentMetadata,
|
|
121
|
+
...metadata,
|
|
122
|
+
content: { document: snapshot.document.body },
|
|
123
|
+
};
|
|
124
|
+
return serializeManagedMarkdown(nextMetadata, snapshot.document.body);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function frontMatterFor(snapshot) {
|
|
128
|
+
const metadata = snapshot.document.managed
|
|
129
|
+
? snapshot.document.metadata
|
|
130
|
+
: createManagedDocument(snapshot.document.title, snapshot.document.body);
|
|
131
|
+
return {
|
|
132
|
+
managed: snapshot.document.managed,
|
|
133
|
+
metadata,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
105
137
|
// @spec CLI-032
|
|
106
138
|
export async function startGPEditorPreview({ filePath, editorHtml }) {
|
|
107
139
|
if (!editorHtml) throw new EditorPreviewError('EDITOR_PREVIEW_UNAVAILABLE', 'GPEditor preview assets are unavailable. Reinstall @gpdoc/cli.');
|
|
108
140
|
const target = path.resolve(filePath);
|
|
109
|
-
const localEditorHtml = editorHtml
|
|
141
|
+
const localEditorHtml = editorHtml
|
|
142
|
+
.replace(/<html([^>]*)>/i, '<html$1 data-gpdoc-cli-editor>')
|
|
143
|
+
.replace(/<\/head>/i, '<style>.share-action{display:none!important}</style></head>');
|
|
110
144
|
await readSnapshot(target);
|
|
111
145
|
const token = crypto.randomBytes(32).toString('base64url');
|
|
112
146
|
let origin = '';
|
|
@@ -116,6 +150,7 @@ export async function startGPEditorPreview({ filePath, editorHtml }) {
|
|
|
116
150
|
const url = new URL(request.url || '/', origin || 'http://127.0.0.1');
|
|
117
151
|
const editorPath = `/editor/${token}`;
|
|
118
152
|
const apiPath = `/api/editor/${token}`;
|
|
153
|
+
const frontMatterPath = `${apiPath}/front-matter`;
|
|
119
154
|
if (request.method === 'GET' && url.pathname === editorPath) {
|
|
120
155
|
response.writeHead(200, {
|
|
121
156
|
'Content-Type': 'text/html; charset=utf-8',
|
|
@@ -127,7 +162,7 @@ export async function startGPEditorPreview({ filePath, editorHtml }) {
|
|
|
127
162
|
response.end(localEditorHtml);
|
|
128
163
|
return;
|
|
129
164
|
}
|
|
130
|
-
if (url.pathname !== apiPath && url.pathname !== `${apiPath}/save`) {
|
|
165
|
+
if (url.pathname !== apiPath && url.pathname !== `${apiPath}/save` && url.pathname !== frontMatterPath) {
|
|
131
166
|
sendJson(response, 404, { error: { code: 'ROUTE_NOT_FOUND', message: 'The editor route was not found.' } });
|
|
132
167
|
return;
|
|
133
168
|
}
|
|
@@ -140,7 +175,12 @@ export async function startGPEditorPreview({ filePath, editorHtml }) {
|
|
|
140
175
|
sendJson(response, 200, { editor });
|
|
141
176
|
return;
|
|
142
177
|
}
|
|
143
|
-
if (request.method
|
|
178
|
+
if (request.method === 'GET' && url.pathname === frontMatterPath) {
|
|
179
|
+
const snapshot = await readSnapshot(target);
|
|
180
|
+
sendJson(response, 200, { frontMatter: frontMatterFor(snapshot) });
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (request.method !== 'POST' || (url.pathname !== `${apiPath}/save` && url.pathname !== frontMatterPath)) {
|
|
144
184
|
sendJson(response, 405, { error: { code: 'METHOD_NOT_ALLOWED', message: 'The editor operation does not support this method.' } });
|
|
145
185
|
return;
|
|
146
186
|
}
|
|
@@ -149,12 +189,16 @@ export async function startGPEditorPreview({ filePath, editorHtml }) {
|
|
|
149
189
|
return;
|
|
150
190
|
}
|
|
151
191
|
const input = await readJson(request);
|
|
152
|
-
if (typeof input.content !== 'string') throw new EditorPreviewError('INVALID_INPUT', 'Editor content must be text.');
|
|
153
192
|
const snapshot = await readSnapshot(target);
|
|
154
193
|
if (String(input.expectedRevision || '') !== snapshot.editor.revision) {
|
|
155
194
|
throw new EditorPreviewError('REVISION_CONFLICT', 'The local file changed. Reload the editor before saving.');
|
|
156
195
|
}
|
|
157
|
-
const nextSource =
|
|
196
|
+
const nextSource = url.pathname === frontMatterPath
|
|
197
|
+
? metadataForSave(snapshot, input.metadata)
|
|
198
|
+
: (() => {
|
|
199
|
+
if (typeof input.content !== 'string') throw new EditorPreviewError('INVALID_INPUT', 'Editor content must be text.');
|
|
200
|
+
return contentForSave(snapshot, input.content);
|
|
201
|
+
})();
|
|
158
202
|
const prospective = snapshotFor(target, nextSource);
|
|
159
203
|
validateDocument(prospective.document);
|
|
160
204
|
await atomicWrite(target, nextSource);
|