pinokiod 7.2.16 → 7.2.18
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/kernel/agent_instructions.js +166 -0
- package/kernel/api/index.js +137 -12
- package/kernel/bin/huggingface.js +1 -1
- package/kernel/environment.js +23 -9
- package/kernel/plugin_sources.js +57 -4
- package/kernel/prototype.js +4 -0
- package/kernel/shell.js +2 -0
- package/kernel/watch/index.js +31 -4
- package/package.json +1 -1
- package/server/features/index.js +4 -4
- package/server/features/{drafts → notes}/index.js +9 -9
- package/server/features/{drafts → notes}/parser.js +12 -7
- package/server/features/notes/public/notes.css +955 -0
- package/server/features/notes/public/notes.js +1149 -0
- package/server/features/{drafts → notes}/registry_import.js +59 -74
- package/server/features/notes/routes.js +156 -0
- package/server/features/notes/service.js +326 -0
- package/server/features/{drafts → notes}/watcher.js +14 -16
- package/server/index.js +61 -30
- package/server/lib/content_validation.js +19 -8
- package/server/lib/workspace_catalog.js +18 -18
- package/server/public/task-launcher.css +11 -3
- package/server/public/tasker.css +336 -0
- package/server/public/tasker.js +407 -0
- package/server/views/d.ejs +33 -2
- package/server/views/partials/menu.ejs +1 -1
- package/server/views/partials/workspace_row.ejs +11 -11
- package/server/views/pre.ejs +1 -1
- package/server/views/task_launch.ejs +10 -10
- package/server/views/tasker.ejs +40 -0
- package/server/views/terminal.ejs +15 -6
- package/server/views/terminals.ejs +0 -1
- package/server/views/workspaces.ejs +2 -1
- package/system/plugin/antigravity/pinokio.js +2 -4
- package/system/plugin/claude/pinokio.js +2 -4
- package/system/plugin/claude-auto/pinokio.js +2 -4
- package/system/plugin/claude-desktop/pinokio.js +2 -4
- package/system/plugin/codex/pinokio.js +2 -4
- package/system/plugin/codex-auto/pinokio.js +2 -4
- package/system/plugin/codex-desktop/pinokio.js +2 -4
- package/system/plugin/crush/pinokio.js +2 -4
- package/system/plugin/cursor/pinokio.js +2 -4
- package/system/plugin/gemini/pinokio.js +2 -4
- package/system/plugin/gemini-auto/pinokio.js +2 -4
- package/system/plugin/qwen/pinokio.js +2 -4
- package/system/plugin/vscode/pinokio.js +2 -4
- package/system/plugin/windsurf/pinokio.js +2 -4
- package/test/plugin-sources.test.js +45 -0
- package/server/features/drafts/public/drafts.js +0 -1569
- package/server/features/drafts/routes.js +0 -68
- package/server/features/drafts/service.js +0 -261
|
@@ -45,7 +45,7 @@ function renderMessage(res, status, title, message) {
|
|
|
45
45
|
</html>`)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function renderImportLauncher(res, { authorizeUrl,
|
|
48
|
+
function renderImportLauncher(res, { authorizeUrl, autoOpen }) {
|
|
49
49
|
res.status(200).send(`<!doctype html>
|
|
50
50
|
<html>
|
|
51
51
|
<head>
|
|
@@ -63,68 +63,48 @@ function renderImportLauncher(res, { authorizeUrl, draftId, registryOrigin, auto
|
|
|
63
63
|
<body>
|
|
64
64
|
<div class="box">
|
|
65
65
|
<h1>Import draft</h1>
|
|
66
|
-
<p id="status">${autoOpen ? "Opening the registry authorization
|
|
66
|
+
<p id="status">${autoOpen ? "Opening the registry authorization page in your browser..." : "Click Open registry to authorize the import."}</p>
|
|
67
67
|
<button id="open" type="button">Open registry</button>
|
|
68
|
-
<div class="muted" style="margin-top:12px;">
|
|
68
|
+
<div class="muted" style="margin-top:12px;">The registry will return to Pinokio after authorization.</div>
|
|
69
69
|
</div>
|
|
70
70
|
<script>
|
|
71
71
|
window.__PINOKIO_DRAFT_IMPORT_VERSION = "metadata-b64";
|
|
72
72
|
const authorizeUrl = ${JSON.stringify(authorizeUrl)};
|
|
73
|
-
const draftId = ${JSON.stringify(draftId)};
|
|
74
|
-
const registryOrigin = ${JSON.stringify(registryOrigin)};
|
|
75
73
|
const autoOpen = ${JSON.stringify(Boolean(autoOpen))};
|
|
76
74
|
const statusEl = document.getElementById("status");
|
|
77
75
|
const openButton = document.getElementById("open");
|
|
78
|
-
let registryWindow = null;
|
|
79
76
|
|
|
80
77
|
function setStatus(message) {
|
|
81
78
|
statusEl.textContent = message;
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
function openRegistry() {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
setStatus("The registry window was blocked. Click Open registry to continue.");
|
|
88
|
-
} else {
|
|
89
|
-
setStatus("Authorize the import in the registry window.");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function completeImport(payload) {
|
|
94
|
-
setStatus("Uploading draft to the registry...");
|
|
95
|
-
const response = await fetch("/registry/draft-import/complete", {
|
|
81
|
+
async function openRegistry() {
|
|
82
|
+
setStatus("Opening registry in your browser...");
|
|
83
|
+
const response = await fetch("/pinokio/open", {
|
|
96
84
|
method: "POST",
|
|
97
85
|
headers: { "Content-Type": "application/json" },
|
|
98
86
|
body: JSON.stringify({
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
registry: payload.registry,
|
|
102
|
-
app: payload.app || ""
|
|
87
|
+
url: authorizeUrl,
|
|
88
|
+
surface: "browser"
|
|
103
89
|
})
|
|
104
90
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const detail = data && data.status ? " (" + data.status + ")" : "";
|
|
108
|
-
throw new Error(((data && data.error) || "Import failed.") + detail);
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new Error("Unable to open registry.");
|
|
109
93
|
}
|
|
110
|
-
|
|
111
|
-
if (registryWindow && !registryWindow.closed) registryWindow.close();
|
|
112
|
-
} catch (_) {}
|
|
113
|
-
window.location.href = data.editUrl;
|
|
94
|
+
setStatus("Authorize the import in your browser.");
|
|
114
95
|
}
|
|
115
96
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (payload.type !== "pinokio:draft-import-token" || !payload.token || !payload.registry) return;
|
|
120
|
-
completeImport(payload).catch((error) => {
|
|
121
|
-
setStatus(error && error.message ? error.message : "Import failed.");
|
|
97
|
+
openButton.addEventListener("click", () => {
|
|
98
|
+
openRegistry().catch((error) => {
|
|
99
|
+
setStatus(error && error.message ? error.message : "Unable to open registry.");
|
|
122
100
|
});
|
|
123
101
|
});
|
|
124
|
-
|
|
125
|
-
openButton.addEventListener("click", openRegistry);
|
|
126
102
|
if (autoOpen) {
|
|
127
|
-
window.setTimeout(
|
|
103
|
+
window.setTimeout(() => {
|
|
104
|
+
openRegistry().catch((error) => {
|
|
105
|
+
setStatus(error && error.message ? error.message : "Unable to open registry.");
|
|
106
|
+
});
|
|
107
|
+
}, 100);
|
|
128
108
|
}
|
|
129
109
|
</script>
|
|
130
110
|
</body>
|
|
@@ -150,10 +130,25 @@ function requestOrigin(req) {
|
|
|
150
130
|
return `${req.protocol || "http"}://${host}`
|
|
151
131
|
}
|
|
152
132
|
|
|
153
|
-
|
|
133
|
+
function buildDraftImportReturnUrl(req, item, bundle) {
|
|
134
|
+
const returnUrl = new URL("/registry/draft-import/callback", requestOrigin(req))
|
|
135
|
+
returnUrl.searchParams.set("draft", item.id)
|
|
136
|
+
if (bundle.appSlug) returnUrl.searchParams.set("app", bundle.appSlug)
|
|
137
|
+
return returnUrl.toString()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildAuthorizeUrl(req, registryBase, item, bundle) {
|
|
141
|
+
const authorizeUrl = new URL("/draft-import/authorize", registryBase)
|
|
142
|
+
authorizeUrl.searchParams.set("handoff", "callback")
|
|
143
|
+
authorizeUrl.searchParams.set("return", buildDraftImportReturnUrl(req, item, bundle))
|
|
144
|
+
if (bundle.appSlug) authorizeUrl.searchParams.set("app", bundle.appSlug)
|
|
145
|
+
return authorizeUrl
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function findNoteById(notes, id) {
|
|
154
149
|
const normalized = String(id || "").trim()
|
|
155
150
|
if (!normalized) return null
|
|
156
|
-
const items = await
|
|
151
|
+
const items = await notes.listPending({})
|
|
157
152
|
return (items || []).find((item) => item && item.id === normalized) || null
|
|
158
153
|
}
|
|
159
154
|
|
|
@@ -173,9 +168,9 @@ function normalizeParent(parent) {
|
|
|
173
168
|
}
|
|
174
169
|
|
|
175
170
|
async function buildDraftBundle(item, query = {}) {
|
|
176
|
-
const markdown = await fs.promises.readFile(item.
|
|
177
|
-
const resultDir = path.dirname(item.
|
|
178
|
-
const titleFallback = item.title || (item.workspaceName ? `
|
|
171
|
+
const markdown = await fs.promises.readFile(item.notePath, "utf8")
|
|
172
|
+
const resultDir = path.dirname(item.notePath)
|
|
173
|
+
const titleFallback = item.title || (item.workspaceName ? `Note for ${item.workspaceName}` : "Note")
|
|
179
174
|
const extracted = extractTitleAndBody(markdown, titleFallback)
|
|
180
175
|
const metadataTitle = item.metadata && typeof item.metadata.title === "string"
|
|
181
176
|
? normalizeTitle(item.metadata.title)
|
|
@@ -200,17 +195,17 @@ function preflightBundle(bundle, options = {}) {
|
|
|
200
195
|
const maxFiles = Number(options.maxFiles || DEFAULT_MAX_FILES)
|
|
201
196
|
const maxFileBytes = Number(options.maxFileBytes || DEFAULT_MAX_FILE_BYTES)
|
|
202
197
|
if (!bundle.title) {
|
|
203
|
-
return "
|
|
198
|
+
return "Note title is missing."
|
|
204
199
|
}
|
|
205
200
|
if (!isRegistryPostPublish(bundle.publish)) {
|
|
206
|
-
return "This
|
|
201
|
+
return "This note is not configured for registry publishing."
|
|
207
202
|
}
|
|
208
203
|
if (bundle.media.length > maxFiles) {
|
|
209
|
-
return `
|
|
204
|
+
return `Note has ${bundle.media.length} media files. The registry limit is ${maxFiles}.`
|
|
210
205
|
}
|
|
211
206
|
const missing = bundle.media.filter((item) => !item.exists)
|
|
212
207
|
if (missing.length > 0) {
|
|
213
|
-
return `
|
|
208
|
+
return `Note references missing media: ${missing.map((item) => item.ref).join(", ")}`
|
|
214
209
|
}
|
|
215
210
|
const oversized = bundle.media.find((item) => item.bytes > maxFileBytes)
|
|
216
211
|
if (oversized) {
|
|
@@ -273,10 +268,10 @@ async function uploadBundle(registryBase, token, bundle) {
|
|
|
273
268
|
return response.data || {}
|
|
274
269
|
}
|
|
275
270
|
|
|
276
|
-
async function uploadDraftFromRequest(
|
|
277
|
-
const item = await
|
|
271
|
+
async function uploadDraftFromRequest(notes, query, token, registryBase, options = {}) {
|
|
272
|
+
const item = await findNoteById(notes, query.draft)
|
|
278
273
|
if (!item) {
|
|
279
|
-
const error = new Error("The local
|
|
274
|
+
const error = new Error("The local note is no longer available.")
|
|
280
275
|
error.status = 404
|
|
281
276
|
throw error
|
|
282
277
|
}
|
|
@@ -291,17 +286,17 @@ async function uploadDraftFromRequest(drafts, query, token, registryBase, option
|
|
|
291
286
|
}
|
|
292
287
|
|
|
293
288
|
function registerDraftImportRoutes(app, options = {}) {
|
|
294
|
-
const
|
|
295
|
-
if (!
|
|
296
|
-
throw new Error("
|
|
289
|
+
const notes = options.notes
|
|
290
|
+
if (!notes) {
|
|
291
|
+
throw new Error("notes is required")
|
|
297
292
|
}
|
|
298
293
|
const defaultRegistryUrl = options.defaultRegistryUrl || "https://beta.pinokio.co"
|
|
299
294
|
const router = express.Router()
|
|
300
295
|
|
|
301
296
|
router.get("/registry/draft-import/authorize-url", asyncHandler(async (req, res) => {
|
|
302
|
-
const item = await
|
|
297
|
+
const item = await findNoteById(notes, req.query.draft)
|
|
303
298
|
if (!item) {
|
|
304
|
-
return res.status(404).json({ error: "The local
|
|
299
|
+
return res.status(404).json({ error: "The local note is no longer available." })
|
|
305
300
|
}
|
|
306
301
|
const bundle = await buildDraftBundle(item, req.query)
|
|
307
302
|
const problem = preflightBundle(bundle, options)
|
|
@@ -312,43 +307,33 @@ function registerDraftImportRoutes(app, options = {}) {
|
|
|
312
307
|
if (!registryBase) {
|
|
313
308
|
return res.status(400).json({ error: "The registry URL is invalid." })
|
|
314
309
|
}
|
|
315
|
-
const authorizeUrl =
|
|
316
|
-
authorizeUrl.searchParams.set("handoff", "post_message")
|
|
317
|
-
authorizeUrl.searchParams.set("origin", requestOrigin(req))
|
|
318
|
-
authorizeUrl.searchParams.set("wait", "1")
|
|
319
|
-
if (bundle.appSlug) authorizeUrl.searchParams.set("app", bundle.appSlug)
|
|
310
|
+
const authorizeUrl = buildAuthorizeUrl(req, registryBase, item, bundle)
|
|
320
311
|
res.setHeader("Cache-Control", "no-store")
|
|
321
312
|
return res.json({
|
|
322
313
|
draftId: item.id,
|
|
323
|
-
authorizeUrl: authorizeUrl.toString()
|
|
324
|
-
registryOrigin: new URL(registryBase).origin
|
|
314
|
+
authorizeUrl: authorizeUrl.toString()
|
|
325
315
|
})
|
|
326
316
|
}))
|
|
327
317
|
|
|
328
318
|
router.get("/registry/draft-import/start", asyncHandler(async (req, res) => {
|
|
329
|
-
const item = await
|
|
319
|
+
const item = await findNoteById(notes, req.query.draft)
|
|
330
320
|
if (!item) {
|
|
331
|
-
return renderMessage(res, 404, "
|
|
321
|
+
return renderMessage(res, 404, "Note not found", "The local note is no longer available.")
|
|
332
322
|
}
|
|
333
323
|
const bundle = await buildDraftBundle(item, req.query)
|
|
334
324
|
const problem = preflightBundle(bundle, options)
|
|
335
325
|
if (problem) {
|
|
336
|
-
return renderMessage(res, 400, "
|
|
326
|
+
return renderMessage(res, 400, "Note is not ready", problem)
|
|
337
327
|
}
|
|
338
328
|
const registryBase = normalizeRegistryBase(req.query.registry, defaultRegistryUrl)
|
|
339
329
|
if (!registryBase) {
|
|
340
330
|
return renderMessage(res, 400, "Registry unavailable", "The registry URL is invalid.")
|
|
341
331
|
}
|
|
342
|
-
const authorizeUrl =
|
|
343
|
-
authorizeUrl.searchParams.set("handoff", "post_message")
|
|
344
|
-
authorizeUrl.searchParams.set("origin", requestOrigin(req))
|
|
345
|
-
if (bundle.appSlug) authorizeUrl.searchParams.set("app", bundle.appSlug)
|
|
332
|
+
const authorizeUrl = buildAuthorizeUrl(req, registryBase, item, bundle)
|
|
346
333
|
res.setHeader("Cache-Control", "no-store")
|
|
347
334
|
res.setHeader("Cross-Origin-Opener-Policy", "unsafe-none")
|
|
348
335
|
return renderImportLauncher(res, {
|
|
349
336
|
authorizeUrl: authorizeUrl.toString(),
|
|
350
|
-
draftId: item.id,
|
|
351
|
-
registryOrigin: new URL(registryBase).origin,
|
|
352
337
|
autoOpen: req.query.auto === "1"
|
|
353
338
|
})
|
|
354
339
|
}))
|
|
@@ -369,7 +354,7 @@ function registerDraftImportRoutes(app, options = {}) {
|
|
|
369
354
|
app: req.body && req.body.app ? String(req.body.app) : ""
|
|
370
355
|
})
|
|
371
356
|
const result = await uploadDraftFromRequest(
|
|
372
|
-
|
|
357
|
+
notes,
|
|
373
358
|
{ draft: req.body && req.body.draft, app: req.body && req.body.app },
|
|
374
359
|
token,
|
|
375
360
|
registryBase,
|
|
@@ -407,7 +392,7 @@ function registerDraftImportRoutes(app, options = {}) {
|
|
|
407
392
|
return renderMessage(res, 400, "Registry unavailable", "The registry URL is invalid.")
|
|
408
393
|
}
|
|
409
394
|
try {
|
|
410
|
-
const result = await uploadDraftFromRequest(
|
|
395
|
+
const result = await uploadDraftFromRequest(notes, req.query, token, registryBase, options)
|
|
411
396
|
if (result && result.editUrl) {
|
|
412
397
|
return res.redirect(String(result.editUrl))
|
|
413
398
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const express = require("express")
|
|
2
|
+
const path = require("path")
|
|
3
|
+
const registerDraftImportRoutes = require("./registry_import")
|
|
4
|
+
|
|
5
|
+
function asyncHandler(fn) {
|
|
6
|
+
return (req, res, next) => {
|
|
7
|
+
Promise.resolve(fn(req, res, next)).catch(next)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isInside(candidate, parent) {
|
|
12
|
+
const relative = path.relative(parent, candidate)
|
|
13
|
+
return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parsePublishConfig(value) {
|
|
17
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
let parsed
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(value)
|
|
23
|
+
} catch (_) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
const target = String(parsed.target || "").trim().toLowerCase()
|
|
30
|
+
const type = String(parsed.type || "post").trim().toLowerCase()
|
|
31
|
+
if (target !== "registry" || type !== "post") {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
const publish = { target: "registry", type: "post" }
|
|
35
|
+
const parent = parsed.parent && typeof parsed.parent === "object" && !Array.isArray(parsed.parent)
|
|
36
|
+
? parsed.parent
|
|
37
|
+
: null
|
|
38
|
+
const parentType = parent ? String(parent.type || "").trim().toLowerCase() : ""
|
|
39
|
+
const parentUrl = parent ? String(parent.url || parent.repoUrl || "").trim() : ""
|
|
40
|
+
if (parentType === "app" && parentUrl) {
|
|
41
|
+
publish.parent = { type: "app", url: parentUrl }
|
|
42
|
+
}
|
|
43
|
+
return publish
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function registerNoteRoutes(app, options = {}) {
|
|
47
|
+
const notes = options.notes
|
|
48
|
+
if (!notes) {
|
|
49
|
+
throw new Error("notes is required")
|
|
50
|
+
}
|
|
51
|
+
const kernel = options.kernel
|
|
52
|
+
|
|
53
|
+
const router = express.Router()
|
|
54
|
+
router.get("/notes", asyncHandler(async (req, res) => {
|
|
55
|
+
const cwd = typeof req.query.cwd === "string" ? req.query.cwd : ""
|
|
56
|
+
if (cwd && typeof notes.inspectWorkspace === "function") {
|
|
57
|
+
const resolvedCwd = path.resolve(cwd)
|
|
58
|
+
const home = kernel && kernel.homedir ? path.resolve(kernel.homedir) : ""
|
|
59
|
+
if (!home || isInside(resolvedCwd, home)) {
|
|
60
|
+
const publish = parsePublishConfig(req.query.publish)
|
|
61
|
+
const note = publish ? { publish } : undefined
|
|
62
|
+
await notes.inspectWorkspace({ cwd: resolvedCwd, note }).catch(() => null)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const items = await notes.listPending({ cwd })
|
|
66
|
+
res.json({
|
|
67
|
+
ok: true,
|
|
68
|
+
items
|
|
69
|
+
})
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
router.get("/notes/:id/media/:index", asyncHandler(async (req, res) => {
|
|
73
|
+
const item = typeof notes.getPendingById === "function"
|
|
74
|
+
? await notes.getPendingById(req.params.id)
|
|
75
|
+
: null
|
|
76
|
+
if (!item) {
|
|
77
|
+
res.status(404).send("Note not found")
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
const index = Number(req.params.index)
|
|
81
|
+
const media = Array.isArray(item.media) && Number.isInteger(index)
|
|
82
|
+
? item.media[index]
|
|
83
|
+
: null
|
|
84
|
+
if (!media || !media.exists || !media.path) {
|
|
85
|
+
res.status(404).send("Media not found")
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
const filePath = path.resolve(media.path)
|
|
89
|
+
const basePath = path.resolve(item.resultDir)
|
|
90
|
+
const relative = path.relative(basePath, filePath)
|
|
91
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
92
|
+
res.status(403).send("Media path is outside the note")
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
res.setHeader("Cache-Control", "no-store")
|
|
96
|
+
res.sendFile(filePath)
|
|
97
|
+
}))
|
|
98
|
+
|
|
99
|
+
router.put("/notes/:id", express.text({ type: "*/*", limit: "6mb" }), asyncHandler(async (req, res) => {
|
|
100
|
+
if (typeof notes.savePendingById !== "function") {
|
|
101
|
+
res.status(501).json({ ok: false, error: "Note editing is unavailable." })
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
const markdown = typeof req.body === "string"
|
|
105
|
+
? req.body
|
|
106
|
+
: (req.body && typeof req.body.markdown === "string"
|
|
107
|
+
? req.body.markdown
|
|
108
|
+
: null)
|
|
109
|
+
if (markdown === null) {
|
|
110
|
+
res.status(400).json({ ok: false, error: "markdown is required" })
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
const revision = typeof req.get("x-pinokio-note-revision") === "string"
|
|
114
|
+
? req.get("x-pinokio-note-revision")
|
|
115
|
+
: (req.body && typeof req.body.revision === "string"
|
|
116
|
+
? req.body.revision
|
|
117
|
+
: "")
|
|
118
|
+
try {
|
|
119
|
+
const item = await notes.savePendingById(req.params.id, { markdown, revision })
|
|
120
|
+
if (!item) {
|
|
121
|
+
res.status(404).json({ ok: false, error: "Note not found" })
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
res.json({ ok: true, item })
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error && error.code === "NOTE_CONFLICT") {
|
|
127
|
+
res.status(409).json({
|
|
128
|
+
ok: false,
|
|
129
|
+
error: error.message,
|
|
130
|
+
item: error.item || null
|
|
131
|
+
})
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
if (error && (error.code === "NOTE_TOO_LARGE" || error.code === "NOTE_INVALID_PATH")) {
|
|
135
|
+
res.status(400).json({ ok: false, error: error.message })
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
throw error
|
|
139
|
+
}
|
|
140
|
+
}))
|
|
141
|
+
|
|
142
|
+
router.get("/notes.js", (req, res) => {
|
|
143
|
+
res.setHeader("Cache-Control", "no-store")
|
|
144
|
+
res.sendFile(path.resolve(__dirname, "public", "notes.js"))
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
router.get("/notes.css", (req, res) => {
|
|
148
|
+
res.setHeader("Cache-Control", "no-store")
|
|
149
|
+
res.sendFile(path.resolve(__dirname, "public", "notes.css"))
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
app.use(router)
|
|
153
|
+
registerDraftImportRoutes(app, options)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = registerNoteRoutes
|