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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vowel",
3
3
  "type": "module",
4
- "version": "0.3.4",
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 readMarkdown(string, filePath, destinationPath, database, config) {
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 readAbstract(abstract, database, config) {
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 writeHTML(destination, database, config) {
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 }) => 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 list = h("section",
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('article', makeHead(target.metadata, target.metadata.prettyURL))
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 markdownReader = {
1285
+ const readMarkdown = {
1156
1286
  extensions: [".md"],
1157
1287
  format: "text",
1158
- readFile: readMarkdown,
1288
+ readFile,
1159
1289
  readResource: readURL,
1160
- transformFile: readAbstract,
1161
- readFolder: readFolder,
1290
+ transformFile,
1291
+ readFolder,
1162
1292
  }
1163
1293
 
1164
- const htmlWriter = {
1294
+ const writeHTML = {
1165
1295
  extensions: [".html"],
1166
1296
  format: "text",
1167
- writeFile: writeHTML
1297
+ writeFile
1168
1298
  }
1169
1299
 
1170
1300
 
1171
1301
  /** @type {Votive.VotivePlugin} */
1172
1302
  const vowelMarkdownPlugin = {
1173
1303
  name: "vowel",
1174
- processors: [markdownReader, htmlWriter],
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
+ }