pinokiod 7.2.8 → 7.2.10

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.
@@ -0,0 +1,169 @@
1
+ const fs = require("fs")
2
+ const path = require("path")
3
+
4
+ const RESULT_RELATIVE_DIR = path.join(".pinokio", "draft")
5
+ const POST_FILENAME = "post.md"
6
+ const METADATA_FILENAME = "pinokio.json"
7
+ const DEFAULT_READY_FILENAME = METADATA_FILENAME
8
+ const PREVIEW_CHARS = 1200
9
+ const MEDIA_EXTENSIONS = new Set([
10
+ ".apng",
11
+ ".avif",
12
+ ".gif",
13
+ ".jpeg",
14
+ ".jpg",
15
+ ".m4a",
16
+ ".mp3",
17
+ ".mp4",
18
+ ".ogg",
19
+ ".png",
20
+ ".svg",
21
+ ".wav",
22
+ ".webm",
23
+ ".webp"
24
+ ])
25
+
26
+ function normalizeWhitespace(value) {
27
+ return String(value || "").replace(/\s+/g, " ").trim()
28
+ }
29
+
30
+ function extractTitle(markdown, workspaceName) {
31
+ const lines = String(markdown || "").split(/\r?\n/)
32
+ for (const line of lines) {
33
+ const match = line.match(/^#\s+(.+?)\s*#*\s*$/)
34
+ if (match && match[1]) {
35
+ return normalizeWhitespace(match[1]).slice(0, 140) || "Draft"
36
+ }
37
+ }
38
+ return workspaceName ? `Draft for ${workspaceName}` : "Draft"
39
+ }
40
+
41
+ function normalizeTitle(value) {
42
+ return normalizeWhitespace(value).slice(0, 160)
43
+ }
44
+
45
+ function parseDraftMetadata(raw) {
46
+ const parsed = JSON.parse(String(raw || ""))
47
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
48
+ return {}
49
+ }
50
+ const metadata = { ...parsed }
51
+ if (typeof metadata.title === "string") {
52
+ metadata.title = normalizeTitle(metadata.title)
53
+ } else {
54
+ delete metadata.title
55
+ }
56
+ return metadata
57
+ }
58
+
59
+ function extractTitleAndBody(markdown, fallbackTitle) {
60
+ const lines = String(markdown || "").split(/\r?\n/)
61
+ for (let i = 0; i < lines.length; i += 1) {
62
+ const match = lines[i].match(/^#\s+(.+?)\s*#*\s*$/)
63
+ if (!match || !match[1]) continue
64
+ const title = normalizeWhitespace(match[1]).slice(0, 160)
65
+ const bodyLines = [...lines.slice(0, i), ...lines.slice(i + 1)]
66
+ while (bodyLines.length && !bodyLines[0].trim()) bodyLines.shift()
67
+ return { title: title || fallbackTitle, body: bodyLines.join("\n").trim() }
68
+ }
69
+ return { title: fallbackTitle, body: String(markdown || "").trim() }
70
+ }
71
+
72
+ function buildExcerpt(markdown) {
73
+ const stripped = String(markdown || "")
74
+ .replace(/```[\s\S]*?```/g, " ")
75
+ .replace(/!\[[^\]]*]\([^)]+\)/g, " ")
76
+ .replace(/\[[^\]]+]\([^)]+\)/g, (match) => {
77
+ const label = match.match(/^\[([^\]]+)]/)
78
+ return label && label[1] ? ` ${label[1]} ` : " "
79
+ })
80
+ .replace(/^#+\s+/gm, "")
81
+ .replace(/[*_`>#-]/g, " ")
82
+ return normalizeWhitespace(stripped).slice(0, PREVIEW_CHARS)
83
+ }
84
+
85
+ function isExternalRef(value) {
86
+ return /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(value)
87
+ }
88
+
89
+ function normalizeMarkdownRef(value) {
90
+ const raw = String(value || "").trim().replace(/^<|>$/g, "")
91
+ if (!raw || raw.includes("\0") || isExternalRef(raw) || path.isAbsolute(raw)) {
92
+ return ""
93
+ }
94
+ const withoutHash = raw.split("#")[0]
95
+ const withoutQuery = withoutHash.split("?")[0]
96
+ if (!withoutQuery) {
97
+ return ""
98
+ }
99
+ let decoded = withoutQuery
100
+ try {
101
+ decoded = decodeURIComponent(withoutQuery)
102
+ } catch (_) {
103
+ }
104
+ const normalized = path.posix.normalize(decoded.replace(/\\/g, "/"))
105
+ if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
106
+ return ""
107
+ }
108
+ return normalized
109
+ }
110
+
111
+ function collectMarkdownRefs(markdown) {
112
+ const refs = []
113
+ const seen = new Set()
114
+ const patterns = [
115
+ /!\[[^\]]*]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g,
116
+ /\[(?:video|audio|media|image|screenshot|file|asset)[^\]]*]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/gi,
117
+ /\[[^\]]+]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g
118
+ ]
119
+ for (const pattern of patterns) {
120
+ let match = null
121
+ while ((match = pattern.exec(markdown))) {
122
+ const ref = normalizeMarkdownRef(match[1])
123
+ if (!ref || seen.has(ref)) continue
124
+ seen.add(ref)
125
+ refs.push(ref)
126
+ }
127
+ }
128
+ return refs
129
+ }
130
+
131
+ async function describeMediaRefs(markdown, baseDir, options = {}) {
132
+ const refs = collectMarkdownRefs(markdown)
133
+ const media = []
134
+ for (const ref of refs) {
135
+ const ext = path.extname(ref).toLowerCase()
136
+ if (options.mediaOnly !== false && !MEDIA_EXTENSIONS.has(ext)) {
137
+ continue
138
+ }
139
+ const filePath = path.resolve(baseDir, ref)
140
+ const relative = path.relative(baseDir, filePath)
141
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
142
+ continue
143
+ }
144
+ const stats = await fs.promises.stat(filePath).catch(() => null)
145
+ media.push({
146
+ ref,
147
+ path: filePath,
148
+ bytes: stats && stats.isFile() ? stats.size : 0,
149
+ mtimeMs: stats && stats.isFile() ? stats.mtimeMs : 0,
150
+ exists: Boolean(stats && stats.isFile())
151
+ })
152
+ }
153
+ return media
154
+ }
155
+
156
+ module.exports = {
157
+ METADATA_FILENAME,
158
+ DEFAULT_READY_FILENAME,
159
+ RESULT_RELATIVE_DIR,
160
+ POST_FILENAME,
161
+ buildExcerpt,
162
+ collectMarkdownRefs,
163
+ describeMediaRefs,
164
+ extractTitle,
165
+ extractTitleAndBody,
166
+ normalizeMarkdownRef,
167
+ normalizeTitle,
168
+ parseDraftMetadata
169
+ }