vowel 0.3.4 → 0.5.0
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/package.json +2 -2
- package/plugins/markdown/index.js +153 -23
- package/stylesheets/DefaultStyles.css +10 -2
- package/utils.js +30 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vowel",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"bin": "bin.js",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"unist-util-visit": "^5.0.0",
|
|
68
68
|
"unist-util-visit-parents": "^6.0.0",
|
|
69
69
|
"url-metadata": "^5.4.1",
|
|
70
|
-
"votive": "^0.2.0",
|
|
71
70
|
"voot": "^0.1.0",
|
|
71
|
+
"votive": "^0.4.0",
|
|
72
72
|
"xast-util-sitemap": "^2.0.0",
|
|
73
73
|
"xast-util-to-xml": "^4.0.0",
|
|
74
74
|
"xml": "^1.0.1",
|
|
@@ -19,7 +19,7 @@ import { h } from 'hastscript'
|
|
|
19
19
|
import { normalizeHeadings } from 'mdast-normalize-headings'
|
|
20
20
|
import { readFileSync } from "fs"
|
|
21
21
|
import { remove } from "unist-util-remove"
|
|
22
|
-
import { testURL } from "./../../utils.js"
|
|
22
|
+
import { testURL, testHashtags, createHashtagPage, toTitleCase, hashtagRegexSingle } from "./../../utils.js"
|
|
23
23
|
import { toHast } from 'mdast-util-to-hast'
|
|
24
24
|
import { toString as hastToString } from 'hast-util-to-string'
|
|
25
25
|
import { toString as mdastToString } from 'mdast-util-to-string'
|
|
@@ -103,14 +103,6 @@ function makeHead(metadata, url) {
|
|
|
103
103
|
return treeMainHead
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
function toTitleCase(string) {
|
|
107
|
-
if (!string) return
|
|
108
|
-
return string.split(" ").map(word => {
|
|
109
|
-
const letters = word.split("")
|
|
110
|
-
letters[0] = letters[0].toUpperCase()
|
|
111
|
-
return letters.join("")
|
|
112
|
-
}).join(" ")
|
|
113
|
-
}
|
|
114
106
|
|
|
115
107
|
function readURL(data) {
|
|
116
108
|
const hast = fromHtml(data)
|
|
@@ -140,7 +132,7 @@ function readURL(data) {
|
|
|
140
132
|
|
|
141
133
|
|
|
142
134
|
/** @type {Votive.ReadText} */
|
|
143
|
-
function
|
|
135
|
+
function readFile(string, filePath, destinationPath, database, config) {
|
|
144
136
|
const mdast = fromMarkdown(string, {
|
|
145
137
|
// Micromark extensions
|
|
146
138
|
extensions: [
|
|
@@ -198,6 +190,7 @@ function readMarkdown(string, filePath, destinationPath, database, config) {
|
|
|
198
190
|
visit(mdast, (node, index, parent) => {
|
|
199
191
|
if (node.type === "text" && parent.children.length === 1 && parent.type === "paragraph") {
|
|
200
192
|
const validURL = testURL(node.value)
|
|
193
|
+
|
|
201
194
|
if (validURL) {
|
|
202
195
|
|
|
203
196
|
jobs.push({
|
|
@@ -205,6 +198,108 @@ function readMarkdown(string, filePath, destinationPath, database, config) {
|
|
|
205
198
|
runner: "text",
|
|
206
199
|
destination: destinationPath
|
|
207
200
|
})
|
|
201
|
+
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const hashtags = testHashtags(node.value)
|
|
206
|
+
|
|
207
|
+
// TODO: Tags should not appear in menus
|
|
208
|
+
// TODO: Make this work when tags are embedded in text
|
|
209
|
+
|
|
210
|
+
if (hashtags) {
|
|
211
|
+
function convertTagsToLinks(value) {
|
|
212
|
+
const match = value.match(hashtagRegexSingle)
|
|
213
|
+
if (match) {
|
|
214
|
+
const remainder = value.slice(match[0].length)
|
|
215
|
+
const child = {
|
|
216
|
+
type: "link",
|
|
217
|
+
url: `/tags/${match[3]}`,
|
|
218
|
+
children: [
|
|
219
|
+
{
|
|
220
|
+
type: "text",
|
|
221
|
+
value: match[0]
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return [
|
|
227
|
+
{
|
|
228
|
+
type: "text",
|
|
229
|
+
value: match[1]
|
|
230
|
+
},
|
|
231
|
+
child,
|
|
232
|
+
{
|
|
233
|
+
type: "text",
|
|
234
|
+
value: match[4]
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
...convertTagsToLinks(remainder)
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
return [
|
|
241
|
+
{
|
|
242
|
+
type: "text",
|
|
243
|
+
value: value
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
parent.children = convertTagsToLinks(node.value)
|
|
249
|
+
|
|
250
|
+
const markdown = `/tags/**`
|
|
251
|
+
const mdast = fromMarkdown(markdown)
|
|
252
|
+
|
|
253
|
+
const extant = database.target.get("tags.html")
|
|
254
|
+
|
|
255
|
+
// FIXME: Update abstract format
|
|
256
|
+
if (!extant) {
|
|
257
|
+
// Delete if unnecessary
|
|
258
|
+
database.target.create({
|
|
259
|
+
path: `tags.html`,
|
|
260
|
+
abstract: mdast,
|
|
261
|
+
metadata: {
|
|
262
|
+
breadcrumb: "Tags",
|
|
263
|
+
title: "Tags",
|
|
264
|
+
prettyURL: "/tags",
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
database.dependency.track({}, "tags", null, destinationPath, "tags.html")
|
|
270
|
+
database.target.markStale("tags.html")
|
|
271
|
+
|
|
272
|
+
if (hashtags) {
|
|
273
|
+
if (!metadata.tags) {
|
|
274
|
+
metadata.tags = []
|
|
275
|
+
} else if (!Array.isArray(metadata.tags)) {
|
|
276
|
+
metadata.tags = []
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
metadata.tags = []
|
|
280
|
+
|
|
281
|
+
hashtags.forEach(hashtag => {
|
|
282
|
+
const title = toTitleCase(hashtag)
|
|
283
|
+
metadata.tags.push(hashtag)
|
|
284
|
+
|
|
285
|
+
const abstract = createHashtagPage(hashtag)
|
|
286
|
+
|
|
287
|
+
const tagMetadata = {
|
|
288
|
+
breadcrumb: title,
|
|
289
|
+
title: title,
|
|
290
|
+
prettyURL: `/tags/${hashtag}`,
|
|
291
|
+
type: "tag",
|
|
292
|
+
tag: hashtag
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const created = database.target.create({
|
|
296
|
+
path: `tags/${hashtag}.html`,
|
|
297
|
+
abstract,
|
|
298
|
+
metadata: tagMetadata
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
})
|
|
302
|
+
}
|
|
208
303
|
}
|
|
209
304
|
}
|
|
210
305
|
})
|
|
@@ -364,7 +459,7 @@ function getMetadata(tree, filePath) {
|
|
|
364
459
|
}
|
|
365
460
|
|
|
366
461
|
/** @type {Votive.ReadAbstract} */
|
|
367
|
-
function
|
|
462
|
+
function transformFile(abstract, database, config) {
|
|
368
463
|
const jobs = []
|
|
369
464
|
return { abstract, jobs }
|
|
370
465
|
}
|
|
@@ -580,13 +675,31 @@ function readFolder(folder, database, config, isRoot) {
|
|
|
580
675
|
}
|
|
581
676
|
|
|
582
677
|
/** @type {Votive.ProcessorWrite} */
|
|
583
|
-
function
|
|
678
|
+
function writeFile(destination, database, config) {
|
|
584
679
|
const isRoot = destination.path === "index.html"
|
|
585
680
|
|
|
681
|
+
// console.log(destination.metadata)
|
|
682
|
+
|
|
683
|
+
if (destination.metadata.type === "tag") {
|
|
684
|
+
if (!destination.metadata.tag) return false
|
|
685
|
+
|
|
686
|
+
const pages = database.target.getManyWithTrackers({
|
|
687
|
+
recursive: true,
|
|
688
|
+
dependent: destination,
|
|
689
|
+
query: {
|
|
690
|
+
tags: destination.metadata.tag
|
|
691
|
+
}
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
if (!pages.length) return false
|
|
695
|
+
}
|
|
696
|
+
|
|
586
697
|
const settings = database.setting.getByFolder(destination.dir + path.sep)
|
|
587
698
|
|
|
588
699
|
const { abstract, metadata, ...rest } = destination
|
|
589
700
|
|
|
701
|
+
if (metadata.tags) metadata.tags = JSON.parse(metadata.tags)
|
|
702
|
+
|
|
590
703
|
/** @param {string} filePath */
|
|
591
704
|
function listFolders(filePath) {
|
|
592
705
|
if (!filePath) return []
|
|
@@ -616,7 +729,9 @@ function writeHTML(destination, database, config) {
|
|
|
616
729
|
dependent: destination.path,
|
|
617
730
|
query: {}
|
|
618
731
|
})
|
|
619
|
-
}).filter(({ path }) =>
|
|
732
|
+
}).filter(({ path, dir }) => {
|
|
733
|
+
return path && path !== "tags.html" && dir !== "tags"
|
|
734
|
+
})
|
|
620
735
|
|
|
621
736
|
const treeStyleSheets = []
|
|
622
737
|
|
|
@@ -907,18 +1022,32 @@ function writeHTML(destination, database, config) {
|
|
|
907
1022
|
const folder = path.relative("/", dir)
|
|
908
1023
|
// const url = new URL(child.value, "thismessage://")
|
|
909
1024
|
const count = url.searchParams.get("count")
|
|
1025
|
+
const tag = url.searchParams.get("tag")
|
|
1026
|
+
const query = tag
|
|
1027
|
+
? { tags: tag }
|
|
1028
|
+
: {}
|
|
1029
|
+
|
|
910
1030
|
const targets = database.target.getManyWithTrackers({
|
|
911
1031
|
folder,
|
|
912
1032
|
recursive,
|
|
913
|
-
query
|
|
1033
|
+
query,
|
|
914
1034
|
dependent: destination.path
|
|
915
1035
|
})
|
|
916
1036
|
|
|
917
1037
|
if (count) targets.splice(Number(count))
|
|
918
1038
|
|
|
919
|
-
const
|
|
1039
|
+
const escapedDir = folder.replace("_", "--").replace(path.sep, "_")
|
|
1040
|
+
const escapedDirs = escapedDir.split("_").filter(a => a).map((segment, index, array) => {
|
|
1041
|
+
return "_" + array.slice(0, index + 1).join("_")
|
|
1042
|
+
})
|
|
1043
|
+
escapedDirs.unshift("_")
|
|
1044
|
+
const dirClasses = escapedDirs.join(".")
|
|
1045
|
+
|
|
1046
|
+
const list = h(`ul.${dirClasses}`,
|
|
920
1047
|
targets.map(target => {
|
|
921
|
-
return h('
|
|
1048
|
+
return h('li',
|
|
1049
|
+
h('article', makeHead(target.metadata, target.metadata.prettyURL))
|
|
1050
|
+
)
|
|
922
1051
|
})
|
|
923
1052
|
)
|
|
924
1053
|
|
|
@@ -1016,6 +1145,7 @@ function writeHTML(destination, database, config) {
|
|
|
1016
1145
|
const globalNavItems = everything.filter(item => {
|
|
1017
1146
|
return item.dir === ""
|
|
1018
1147
|
&& item.path !== "index.html"
|
|
1148
|
+
&& item.path !== "tags.html"
|
|
1019
1149
|
})
|
|
1020
1150
|
.map(getChildren)
|
|
1021
1151
|
|
|
@@ -1152,26 +1282,26 @@ function router(args) {
|
|
|
1152
1282
|
|
|
1153
1283
|
|
|
1154
1284
|
/** @type {Votive.VotiveProcessor} */
|
|
1155
|
-
const
|
|
1285
|
+
const readMarkdown = {
|
|
1156
1286
|
extensions: [".md"],
|
|
1157
1287
|
format: "text",
|
|
1158
|
-
readFile
|
|
1288
|
+
readFile,
|
|
1159
1289
|
readResource: readURL,
|
|
1160
|
-
transformFile
|
|
1161
|
-
readFolder
|
|
1290
|
+
transformFile,
|
|
1291
|
+
readFolder,
|
|
1162
1292
|
}
|
|
1163
1293
|
|
|
1164
|
-
const
|
|
1294
|
+
const writeHTML = {
|
|
1165
1295
|
extensions: [".html"],
|
|
1166
1296
|
format: "text",
|
|
1167
|
-
writeFile
|
|
1297
|
+
writeFile
|
|
1168
1298
|
}
|
|
1169
1299
|
|
|
1170
1300
|
|
|
1171
1301
|
/** @type {Votive.VotivePlugin} */
|
|
1172
1302
|
const vowelMarkdownPlugin = {
|
|
1173
1303
|
name: "vowel",
|
|
1174
|
-
processors: [
|
|
1304
|
+
processors: [readMarkdown, writeHTML],
|
|
1175
1305
|
router
|
|
1176
1306
|
}
|
|
1177
1307
|
|
|
@@ -226,15 +226,23 @@
|
|
|
226
226
|
border-left: 5px solid var(--primary-border-color);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
article {
|
|
229
|
+
main>article {
|
|
230
230
|
border: 1px solid var(--primary-border-color);
|
|
231
|
-
background: var(--soft-background);
|
|
232
231
|
padding: 2rem 2rem 2rem;
|
|
233
232
|
border-radius: calc(1rem + 5px);
|
|
234
233
|
display: flex;
|
|
235
234
|
flex-direction: column;
|
|
236
235
|
}
|
|
237
236
|
|
|
237
|
+
main>ul._ {
|
|
238
|
+
list-style: none;
|
|
239
|
+
padding: 0;
|
|
240
|
+
|
|
241
|
+
& article {
|
|
242
|
+
padding-bottom: 1em;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
238
246
|
th,
|
|
239
247
|
td {
|
|
240
248
|
border-bottom: 1px solid var(--border-color-softer);
|
package/utils.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {fromMarkdown} from 'mdast-util-from-markdown'
|
|
2
|
+
import {toHast} from 'mdast-util-to-hast'
|
|
3
|
+
import {toHtml} from 'hast-util-to-html'
|
|
4
|
+
|
|
5
|
+
|
|
1
6
|
/** @param {string} text */
|
|
2
7
|
export function testURL(text) {
|
|
3
8
|
if (text.match(/^https?:\/\/[\S]+$/)) {
|
|
@@ -8,3 +13,28 @@ export function testURL(text) {
|
|
|
8
13
|
}
|
|
9
14
|
}
|
|
10
15
|
}
|
|
16
|
+
|
|
17
|
+
export const hashtagRegexSingle = /(^|\s)(#([\w\-\/]+))($|\b)/
|
|
18
|
+
export const hashtagRegexGlobal = /(?:^|\s)(#[\w\-\/]+)(?:$|\b)/g
|
|
19
|
+
|
|
20
|
+
export function testHashtags(text) {
|
|
21
|
+
const match = text.match(hashtagRegexGlobal)
|
|
22
|
+
if(match) return match.map(a => a.trim().slice(1))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export function toTitleCase(string) {
|
|
27
|
+
if (!string) return
|
|
28
|
+
return string.split(" ").map(word => {
|
|
29
|
+
const letters = word.split("")
|
|
30
|
+
letters[0] = letters[0].toUpperCase()
|
|
31
|
+
return letters.join("")
|
|
32
|
+
}).join(" ")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createHashtagPage(tag) {
|
|
36
|
+
const markdown = `# ${toTitleCase(tag)}\n\n/**?tag=${tag}`
|
|
37
|
+
const mdast = fromMarkdown(markdown)
|
|
38
|
+
|
|
39
|
+
return mdast
|
|
40
|
+
}
|