imdone-core 2.0.0 → 2.0.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.
Files changed (160) hide show
  1. package/.dependency-cruiser.cjs +412 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  3. package/.github/workflows/ci.yml +24 -0
  4. package/.imdoneignore +13 -0
  5. package/.mocharc.json +10 -0
  6. package/.prettierrc +6 -0
  7. package/CHANGELOG.md +160 -0
  8. package/_index.md +7 -0
  9. package/_navbar.md +1 -0
  10. package/build.js +57 -0
  11. package/esm-migration.md +446 -0
  12. package/index.html +184 -0
  13. package/lib/FileProjectContext.js +13 -0
  14. package/lib/ProjectContext.js +43 -0
  15. package/lib/adapters/date-time.js +15 -0
  16. package/lib/adapters/environment.js +3 -0
  17. package/lib/adapters/file-gateway.js +154 -0
  18. package/lib/adapters/git-download.js +55 -0
  19. package/lib/adapters/index.js +9 -0
  20. package/lib/adapters/logger.js +136 -0
  21. package/lib/adapters/markdown.js +180 -0
  22. package/lib/adapters/parsers/DateLanguageParser.js +12 -0
  23. package/lib/adapters/parsers/content-transformer.js +116 -0
  24. package/lib/adapters/parsers/file/LocalFileParserFactory.js +36 -0
  25. package/lib/adapters/parsers/file/ReadStreamCodeParser.js +126 -0
  26. package/lib/adapters/parsers/file/ReadStreamMarkdownParser.js +17 -0
  27. package/lib/adapters/parsers/file/ReadStreamTaskParser.js +106 -0
  28. package/lib/adapters/parsers/file/index.js +4 -0
  29. package/lib/adapters/parsers/function-parser.js +25 -0
  30. package/lib/adapters/parsers/index.js +5 -0
  31. package/lib/adapters/parsers/task/CardContentParser.js +367 -0
  32. package/lib/adapters/parsers/task/CheckStyleTaskParser.js +28 -0
  33. package/lib/adapters/parsers/task/CodeStyleTaskParser.js +24 -0
  34. package/lib/adapters/parsers/task/HashStyleTaskParser.js +27 -0
  35. package/lib/adapters/parsers/task/MarkdownStyleTaskParser.js +24 -0
  36. package/lib/adapters/parsers/task/TaskParser.js +38 -0
  37. package/lib/adapters/parsers/task/index.js +6 -0
  38. package/lib/adapters/storage/config.js +44 -0
  39. package/lib/adapters/storage/index.js +2 -0
  40. package/lib/adapters/storage/tags.js +29 -0
  41. package/lib/adapters/yaml.js +9 -0
  42. package/lib/card.js +299 -0
  43. package/lib/checksum.js +22 -0
  44. package/lib/config.js +249 -0
  45. package/lib/constants.js +68 -0
  46. package/lib/context/ApplicationContext.js +35 -0
  47. package/lib/context/index.js +1 -0
  48. package/lib/default-settings.js +37 -0
  49. package/lib/domain/entities/View.js +23 -0
  50. package/lib/domain/entities/index.js +1 -0
  51. package/lib/domain/index.js +2 -0
  52. package/lib/domain/usecases/GetViewsUsecase.js +44 -0
  53. package/lib/domain/usecases/SaveViewUsecase.js +51 -0
  54. package/lib/domain/usecases/index.js +2 -0
  55. package/lib/file.js +1391 -0
  56. package/lib/get-tasks-to-modify.js +117 -0
  57. package/lib/languages.js +113 -0
  58. package/lib/list.js +56 -0
  59. package/lib/migrate-config.js +165 -0
  60. package/lib/mixins/index.js +2 -0
  61. package/lib/mixins/repo-fs-store.js +359 -0
  62. package/lib/mixins/repo-watched-fs-store.js +206 -0
  63. package/lib/plugins/archive-plugin.js +72 -0
  64. package/lib/plugins/default-board-actions-plugin.js +56 -0
  65. package/lib/plugins/default-board-properties-plugin.js +101 -0
  66. package/lib/plugins/default-plugins.js +7 -0
  67. package/lib/plugins/epic-plugin.js +177 -0
  68. package/lib/plugins/extension-plugin.js +94 -0
  69. package/lib/plugins/index.js +9 -0
  70. package/lib/plugins/persist-tags-plugin.js +14 -0
  71. package/lib/plugins/plugin-manager.js +458 -0
  72. package/lib/plugins/plugin-registry.js +57 -0
  73. package/lib/project-factory.js +42 -0
  74. package/lib/project.js +864 -0
  75. package/lib/repository.js +1514 -0
  76. package/lib/task.js +780 -0
  77. package/lib/tools.js +176 -0
  78. package/lib/usecases/actions.js +32 -0
  79. package/lib/usecases/get-imdone-url.js +6 -0
  80. package/lib/usecases/get-project-tags.js +7 -0
  81. package/lib/usecases/get-tasks-in-file.js +26 -0
  82. package/lib/usecases/index.js +4 -0
  83. package/package.json +2 -6
  84. package/scripts/delete-markdown-indexes.js +87 -0
  85. package/scripts/generate-index.js +35 -0
  86. package/scripts/index-markdown.js +55 -0
  87. package/tsconfig.json +15 -0
  88. package/types/FileProjectContext.d.ts +4 -0
  89. package/types/ProjectContext.d.ts +9 -0
  90. package/types/adapters/date-time.d.ts +2 -0
  91. package/types/adapters/environment.d.ts +1 -0
  92. package/types/adapters/file-gateway.d.ts +51 -0
  93. package/types/adapters/git-download.d.ts +1 -0
  94. package/types/adapters/index.d.ts +9 -0
  95. package/types/adapters/logger.d.ts +24 -0
  96. package/types/adapters/markdown.d.ts +13 -0
  97. package/types/adapters/parsers/DateLanguageParser.d.ts +1 -0
  98. package/types/adapters/parsers/content-transformer.d.ts +10 -0
  99. package/types/adapters/parsers/file/LocalFileParserFactory.d.ts +5 -0
  100. package/types/adapters/parsers/file/ReadStreamCodeParser.d.ts +13 -0
  101. package/types/adapters/parsers/file/ReadStreamMarkdownParser.d.ts +7 -0
  102. package/types/adapters/parsers/file/ReadStreamTaskParser.d.ts +19 -0
  103. package/types/adapters/parsers/file/index.d.ts +4 -0
  104. package/types/adapters/parsers/function-parser.d.ts +1 -0
  105. package/types/adapters/parsers/index.d.ts +5 -0
  106. package/types/adapters/parsers/task/CardContentParser.d.ts +45 -0
  107. package/types/adapters/parsers/task/CheckStyleTaskParser.d.ts +12 -0
  108. package/types/adapters/parsers/task/CodeStyleTaskParser.d.ts +14 -0
  109. package/types/adapters/parsers/task/HashStyleTaskParser.d.ts +13 -0
  110. package/types/adapters/parsers/task/MarkdownStyleTaskParser.d.ts +14 -0
  111. package/types/adapters/parsers/task/TaskParser.d.ts +7 -0
  112. package/types/adapters/parsers/task/index.d.ts +6 -0
  113. package/types/adapters/storage/config.d.ts +6 -0
  114. package/types/adapters/storage/index.d.ts +2 -0
  115. package/types/adapters/storage/tags.d.ts +3 -0
  116. package/types/adapters/yaml.d.ts +2 -0
  117. package/types/card.d.ts +166 -0
  118. package/types/checksum.d.ts +8 -0
  119. package/types/config.d.ts +61 -0
  120. package/types/constants.d.ts +46 -0
  121. package/types/context/ApplicationContext.d.ts +24 -0
  122. package/types/context/index.d.ts +1 -0
  123. package/types/default-settings.d.ts +2 -0
  124. package/types/domain/entities/FileProjectContext.d.ts +5 -0
  125. package/types/domain/entities/View.d.ts +17 -0
  126. package/types/domain/entities/index.d.ts +1 -0
  127. package/types/domain/index.d.ts +2 -0
  128. package/types/domain/usecases/GetViewsUsecase.d.ts +4 -0
  129. package/types/domain/usecases/SaveViewUsecase.d.ts +8 -0
  130. package/types/domain/usecases/index.d.ts +2 -0
  131. package/types/file.d.ts +344 -0
  132. package/types/get-tasks-to-modify.d.ts +3 -0
  133. package/types/index.d.ts +23 -0
  134. package/types/languages.d.ts +649 -0
  135. package/types/list.d.ts +30 -0
  136. package/types/migrate-config.d.ts +1 -0
  137. package/types/mixins/index.d.ts +2 -0
  138. package/types/mixins/repo-fs-store.d.ts +2 -0
  139. package/types/mixins/repo-watched-fs-store.d.ts +2 -0
  140. package/types/plugins/archive-plugin.d.ts +8 -0
  141. package/types/plugins/default-board-actions-plugin.d.ts +9 -0
  142. package/types/plugins/default-board-properties-plugin.d.ts +16 -0
  143. package/types/plugins/default-plugins.d.ts +6 -0
  144. package/types/plugins/epic-plugin.d.ts +19 -0
  145. package/types/plugins/extension-plugin.d.ts +15 -0
  146. package/types/plugins/index.d.ts +9 -0
  147. package/types/plugins/persist-tags-plugin.d.ts +4 -0
  148. package/types/plugins/plugin-manager.d.ts +68 -0
  149. package/types/plugins/plugin-registry.d.ts +1 -0
  150. package/types/project-factory.d.ts +14 -0
  151. package/types/project.d.ts +158 -0
  152. package/types/repository.d.ts +996 -0
  153. package/types/task.d.ts +202 -0
  154. package/types/tools.d.ts +29 -0
  155. package/types/usecases/actions.d.ts +7 -0
  156. package/types/usecases/get-imdone-url.d.ts +1 -0
  157. package/types/usecases/get-project-tags.d.ts +1 -0
  158. package/types/usecases/get-tasks-in-file.d.ts +4 -0
  159. package/types/usecases/index.d.ts +4 -0
  160. package/vitest.config.js +13 -0
@@ -0,0 +1,13 @@
1
+ import { toNumber } from "./adapters/parsers/task/CardContentParser.js";
2
+ export class FileProjectContext {
3
+ constructor() {}
4
+ getOrder(list, order) {
5
+ return toNumber(order)
6
+ }
7
+
8
+ getProject() {
9
+ return
10
+ }
11
+
12
+ }
13
+
@@ -0,0 +1,43 @@
1
+ import { FileProjectContext } from './FileProjectContext.js'
2
+ import { isNumber, toNumber } from './adapters/parsers/task/CardContentParser.js'
3
+
4
+ export class ProjectContext extends FileProjectContext {
5
+ constructor(repo) {
6
+ super()
7
+ this.repo = repo
8
+ }
9
+
10
+ getOrder(list, order) {
11
+ const config = this.repo.config
12
+ if (config && config.keepEmptyPriority && order === undefined) return
13
+ const tasks =
14
+ list && this.repo.listExists(list) ? this.repo.getTasksInList(list) : []
15
+ return this.determineOrder(config, order, tasks)
16
+ }
17
+
18
+ get config() {
19
+ return this.repo.config
20
+ }
21
+
22
+ toJSON() {
23
+ return null
24
+ }
25
+
26
+ determineOrder(config, order, tasks) {
27
+ if (isNumber(order)) return toNumber(order)
28
+ if (!config) return
29
+ if (config.keepEmptyPriority) return
30
+ if (tasks) {
31
+ if (tasks.length == 0) {
32
+ return 0
33
+ } else if (config.isAddNewCardsToTop()) {
34
+ return (isNumber(tasks[0].order) ? toNumber(tasks[0].order) : 0) - 10
35
+ } else {
36
+ const lastTask = tasks[tasks.length - 1]
37
+ return (lastTask && isNumber(lastTask.order) ? lastTask.order : 0) + 10
38
+ }
39
+ }
40
+ return 0
41
+ }
42
+
43
+ }
@@ -0,0 +1,15 @@
1
+ import moment from "moment";
2
+ let innerNoDST = false;
3
+
4
+ export function setNoDST(noDST) {
5
+ innerNoDST = noDST;
6
+ }
7
+
8
+ export function getIsoDateWithOffset (date) {
9
+ const momentDate = moment(date);
10
+ if (momentDate.isDST() && innerNoDST) {
11
+ const offset = momentDate.utcOffset();
12
+ momentDate.utcOffset(offset - 60);
13
+ }
14
+ return momentDate.format();
15
+ }
@@ -0,0 +1,3 @@
1
+ export function isDev() {
2
+ return /dev/i.test(process.env.NODE_ENV)
3
+ }
@@ -0,0 +1,154 @@
1
+ import _path from 'path'
2
+ import _fs from 'fs'
3
+ import sanitize from 'sanitize-filename'
4
+ import { logger } from './logger.js'
5
+
6
+ let fs = _fs
7
+
8
+ export async function exists(path) {
9
+ try {
10
+ return await stat(path)
11
+ } catch {
12
+ return false
13
+ }
14
+ }
15
+
16
+ export function statSync(path) {
17
+ logger.warn('statSync call')
18
+ try {
19
+ return fs.statSync(path)
20
+ } catch {
21
+ }
22
+ }
23
+
24
+ export function init(fileSystem = fs) {
25
+ fs = fileSystem
26
+ }
27
+
28
+ export const sep = _path.sep
29
+
30
+ export function existsSync(...args) {
31
+ logger.warn('existsSync call')
32
+ return fs.existsSync.apply({}, args)
33
+ }
34
+
35
+ export const stat = fs.promises.stat
36
+
37
+ export const lstat = fs.promises.lstat
38
+
39
+ export const lstatSync = fs.lstatSync
40
+
41
+ export function readFileSync(...args) {
42
+ logger.warn('readFileSync call')
43
+ return fs.readFileSync.apply({}, args)
44
+ }
45
+
46
+ export function appendFileSync(...args) {
47
+ logger.warn('appendFileSync call')
48
+ return fs.appendFileSync.apply({}, args)
49
+ }
50
+
51
+ export function writeFileSync(...args) {
52
+ logger.warn('writeFileSync call')
53
+ return fs.writeFileSync.apply({}, args)
54
+ }
55
+
56
+ export const writeFile = fs.promises.writeFile
57
+
58
+ export async function readdir(...args) {
59
+ return fs.promises.readdir.apply({}, args)
60
+ }
61
+
62
+ export function unlinkSync(...args) {
63
+ logger.warn('unlinkSync call')
64
+ return fs.unlinkSync.apply({}, args)
65
+ }
66
+
67
+ export const unlink = fs.promises.unlink
68
+
69
+ export const mkdir = fs.promises.mkdir
70
+
71
+ export const readFile = fs.promises.readFile
72
+
73
+ export function readdirSyncRecursive(path) {
74
+ logger.warn('readdirSyncRecursive call')
75
+ // implement this function
76
+ let results = []
77
+ let list = fs.readdirSync(path)
78
+ list.forEach(function(file) {
79
+ file = _path.join(path, file)
80
+ let stat = fs.statSync(file)
81
+ if (stat && stat.isDirectory()) {
82
+ results = results.concat(readdirSyncRecursive(file))
83
+ } else {
84
+ results.push(file)
85
+ }
86
+ })
87
+ return results
88
+ }
89
+
90
+ export function readdirSync(path) {
91
+ logger.warn('readdirSync call')
92
+ return fs.readdirSync(path, { withFileTypes: true })
93
+ }
94
+
95
+ export function sanitizeFileName(fileName, replaceSpacesWith) {
96
+ // Remove markdown emoji
97
+ fileName = fileName.replace(/:\w+:/g, '').trim()
98
+ fileName = sanitize(fileName)
99
+ if (replaceSpacesWith)
100
+ fileName = fileName.replace(/ /g, replaceSpacesWith)
101
+ return fileName
102
+ }
103
+
104
+ function statsProps(stats) {
105
+ return {
106
+ isFile: stats.isFile(),
107
+ isDirectory: stats.isDirectory()
108
+ }
109
+ }
110
+
111
+ export async function preparePathForWriting(path, directory = false) {
112
+ let stats = await exists(path)
113
+ if (stats) return statsProps(stats)
114
+
115
+ let { dir } = directory ? { dir: path } : _path.parse(path)
116
+ stats = await exists(dir)
117
+ if (stats) return statsProps(stats)
118
+
119
+ await mkdir(dir, { recursive: true })
120
+ stats = await exists(dir)
121
+ return statsProps(stats)
122
+ }
123
+
124
+ export async function cp(src, dest, opts) {
125
+ return fs.promises.cp(src, dest, opts)
126
+ }
127
+
128
+ export async function rm(path, opts) {
129
+ return fs.promises.rm(path, opts)
130
+ }
131
+
132
+ export default {
133
+ cp,
134
+ rm,
135
+ exists,
136
+ stat,
137
+ lstat,
138
+ lstatSync,
139
+ readFileSync,
140
+ appendFileSync,
141
+ writeFileSync,
142
+ writeFile,
143
+ readdir,
144
+ unlink,
145
+ mkdir,
146
+ readFile,
147
+ readdirSyncRecursive,
148
+ readdirSync,
149
+ sanitizeFileName,
150
+ preparePathForWriting,
151
+ unlinkSync,
152
+ existsSync,
153
+ sep
154
+ }
@@ -0,0 +1,55 @@
1
+ import fetch from 'node-fetch';
2
+ import AdmZip from 'adm-zip';
3
+ import { cp, rm } from './file-gateway.js'
4
+ import path from 'node:path'
5
+
6
+ export async function downloadPlugin(repo, dest) {
7
+ const url = github(normalize(repo));
8
+ const response = await fetch(url);
9
+
10
+ if (!response.ok) {
11
+ throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
12
+ }
13
+
14
+ const arrayBuffer = await response.arrayBuffer();
15
+ const buffer = Buffer.from(arrayBuffer);
16
+ const zip = AdmZip(buffer);
17
+ await zip.extractAllToAsync(dest, true);
18
+ const zipEntries = zip.getEntries();
19
+ const rootFolder = zipEntries[0].entryName.split(path.sep)[0];
20
+ const rootFolderPath = path.join(dest, rootFolder)
21
+ await cp(rootFolderPath, dest, {
22
+ recursive: true,
23
+ force: true, // overwrite if needed
24
+ errorOnExist: false,
25
+ });
26
+ await rm(rootFolderPath, {
27
+ recursive: true,
28
+ force: true, // overwrite if needed
29
+ });
30
+ }
31
+
32
+ function github(repo) {
33
+ return 'https://github.com/'
34
+ + repo.owner
35
+ + '/'
36
+ + repo.name
37
+ + '/archive/'
38
+ + repo.branch
39
+ + '.zip';
40
+ }
41
+
42
+ function normalize(string) {
43
+ let [owner, name] = string.split('/');
44
+ let branch = 'master';
45
+
46
+ if (name.includes('#')) {
47
+ [name, branch] = name.split('#');
48
+ }
49
+
50
+ return {
51
+ owner,
52
+ name,
53
+ branch
54
+ };
55
+ }
@@ -0,0 +1,9 @@
1
+ export * from './date-time.js';
2
+ export * from './environment.js';
3
+ export * from './file-gateway.js';
4
+ export * from './git-download.js';
5
+ export * from './logger.js';
6
+ export * from './markdown.js';
7
+ export * from './yaml.js';
8
+ export * as parsers from './parsers/index.js';
9
+ export * as storage from './storage/index.js';
@@ -0,0 +1,136 @@
1
+ import { info, error as consoleError, warn as consoleWarn } from 'console'
2
+
3
+ // Log levels with numeric values for comparison
4
+ const LogLevel = {
5
+ ERROR: 0,
6
+ WARN: 1,
7
+ INFO: 2,
8
+ DEBUG: 3,
9
+ TRACE: 4,
10
+ NONE: -1 // Use this to disable all logging
11
+ }
12
+
13
+ function getCallerInfo() {
14
+ const err = new Error();
15
+ const stack = err.stack?.split('\n');
16
+
17
+ if (!stack || stack.length < 5) return {};
18
+
19
+ // stack[0] is "Error"
20
+ // stack[1] is this function (getCallerInfo)
21
+ // stack[2] is the formatMessage method
22
+ // stack[3] is the logger method (log, info, debug, etc.)
23
+ // stack[4] is the actual caller we want
24
+
25
+ const callerLine = stack[4] || stack[3]; // Get the actual caller, fallback to logger method if needed
26
+ const match = callerLine.match(/at (.+?) \((.+):(\d+):(\d+)\)/) ||
27
+ callerLine.match(/at (.+):(\d+):(\d+)/);
28
+
29
+ if (match) {
30
+ if (match.length === 5) {
31
+ return {
32
+ functionName: match[1],
33
+ file: match[2],
34
+ line: parseInt(match[3], 10),
35
+ column: parseInt(match[4], 10)
36
+ };
37
+ } else if (match.length === 4) {
38
+ return {
39
+ functionName: '<anonymous>',
40
+ file: match[1],
41
+ line: parseInt(match[2], 10),
42
+ column: parseInt(match[3], 10)
43
+ };
44
+ }
45
+ }
46
+
47
+ return {};
48
+ }
49
+
50
+ class Logger {
51
+ constructor() {
52
+ // Default log level
53
+ this.level = LogLevel.INFO
54
+ }
55
+
56
+ // Setter for log level
57
+ setLevel(level) {
58
+ if (level in LogLevel) {
59
+ this.level = LogLevel[level]
60
+ } else if (typeof level === 'number' && level >= -1 && level <= 4) {
61
+ this.level = level
62
+ } else {
63
+ consoleWarn(`Invalid log level: ${level}. Using default INFO level.`)
64
+ }
65
+ return this
66
+ }
67
+
68
+ // Check if the provided level should be logged
69
+ shouldLog(level) {
70
+ return level <= this.level
71
+ }
72
+
73
+ formatMessage(args) {
74
+ const info = getCallerInfo();
75
+ const prefix = info.file ? `[${info.file}:${info.line}] ${info.functionName} - ` : '';
76
+
77
+ if (typeof args[0] === 'string') {
78
+ args[0] = prefix + args[0];
79
+ } else {
80
+ args.unshift(prefix);
81
+ }
82
+
83
+ return args;
84
+ }
85
+
86
+ log(...args) {
87
+ if (this.shouldLog(LogLevel.INFO)) {
88
+ info(...this.formatMessage(args));
89
+ }
90
+ }
91
+
92
+ error(...args) {
93
+ if (this.shouldLog(LogLevel.ERROR)) {
94
+ consoleError(...this.formatMessage(args));
95
+ }
96
+ }
97
+
98
+ warn(...args) {
99
+ if (this.shouldLog(LogLevel.WARN)) {
100
+ consoleWarn(...this.formatMessage(args));
101
+ }
102
+ }
103
+
104
+ info(...args) {
105
+ if (this.shouldLog(LogLevel.INFO)) {
106
+ info(...this.formatMessage(args));
107
+ }
108
+ }
109
+
110
+ debug(...args) {
111
+ if (this.shouldLog(LogLevel.DEBUG)) {
112
+ info(`[DEBUG]`, ...this.formatMessage(args));
113
+ }
114
+ }
115
+
116
+ trace(...args) {
117
+ if (this.shouldLog(LogLevel.TRACE)) {
118
+ info(`[TRACE]`, ...this.formatMessage(args));
119
+ }
120
+ }
121
+
122
+ time(label) {
123
+ if (this.shouldLog(LogLevel.DEBUG)) {
124
+ console.time(label)
125
+ }
126
+ }
127
+
128
+ timeEnd(label) {
129
+ if (this.shouldLog(LogLevel.DEBUG)) {
130
+ console.timeEnd(label)
131
+ }
132
+ }
133
+ }
134
+
135
+ export const logger = new Logger()
136
+ export { LogLevel }
@@ -0,0 +1,180 @@
1
+ import checkbox from 'markdown-it-checkbox';
2
+ import imageSize from 'markdown-it-imsize';
3
+ import emoji from 'markdown-it-emoji';
4
+ import mark from 'markdown-it-mark';
5
+ import _removeMD from 'remove-markdown';
6
+ import path from 'path';
7
+ import markdownIt from 'markdown-it';
8
+ import { createRequire } from 'module';
9
+
10
+ export const allEmoji = createRequire(import.meta.url)('markdown-it-emoji/lib/data/full.json')
11
+
12
+ let _platform = process.platform
13
+
14
+ export function extractWikilinkTopics(markdown) {
15
+ const wikilinkRegex = /\[\[([a-zA-Z0-9\u00C0-\u017F/_\-. ]+)(?:\|([^\]]+))?\]\]/g;
16
+ const topics = [];
17
+ let match;
18
+
19
+ while ((match = wikilinkRegex.exec(markdown)) !== null) {
20
+ topics.push(match[1].trim()); // Always extract the topic (first capture group)
21
+ }
22
+
23
+ return topics;
24
+ }
25
+
26
+ export function _setPlatform(platform) {
27
+ _platform = platform
28
+ }
29
+
30
+ export function renderMarkdown (markdown, filePath) {
31
+ markdown = renderCancelledTasks(markdown)
32
+ return getRenderer(filePath).render(markdown)
33
+ }
34
+
35
+ export function removeMD() {
36
+ return _removeMD.apply({}, arguments)
37
+ }
38
+
39
+ function getRenderer(filePath) {
40
+ const md = markdownIt({html: true, breaks: true})
41
+
42
+ md.use(checkbox)
43
+ .use(mark)
44
+ .use(emoji)
45
+ .use(imageSize)
46
+ .use(obsidianTopicLinks(filePath))
47
+ .use(replaceImagePaths(filePath))
48
+ .use(replaceLinkPathsAndTitles(filePath))
49
+ .validateLink = () => true
50
+
51
+ return md
52
+ }
53
+
54
+ function obsidianTopicLinks(filePath) {
55
+ return function(md) {
56
+ const obsidianLinkRegex = /^\[\[([a-zA-Z0-9\u00C0-\u017F/_\-. ]+)(?:\|([^\]]+))?\]\]/; // No 'g' flag
57
+
58
+ function obsidianLinkRenderer(state, silent) {
59
+ const pos = state.pos;
60
+ const match = state.src.slice(pos).match(obsidianLinkRegex); // Always start from `pos`
61
+
62
+ if (!match) return false;
63
+ if (silent) return true; // Don't process further in silent mode
64
+
65
+ const fullMatch = match[0];
66
+ let topic = match[1].trim();
67
+ const alias = match[2] ? match[2].trim() : topic;
68
+ if (!topic.endsWith('.md')) topic = `${topic}.md`;
69
+ const unencodedHref = resolveFileUrl(topic, filePath);
70
+ const href = encodeURI(unencodedHref);
71
+
72
+ // Create a new token
73
+ const token = state.push("obsidian_link", "", 0);
74
+ token.content = `<a href="${href}" title="${unencodedHref}">${md.utils.escapeHtml(alias)}</a>`;
75
+
76
+ // Move parsing position forward
77
+ state.pos += fullMatch.length;
78
+ return true;
79
+ }
80
+
81
+ function render(tokens, idx) {
82
+ return tokens[idx].content;
83
+ }
84
+
85
+ md.inline.ruler.before("link", "obsidian_link", obsidianLinkRenderer);
86
+ md.renderer.rules.obsidian_link = render;
87
+ }
88
+ }
89
+
90
+
91
+ function replaceImagePaths(filePath) {
92
+
93
+ return function(md) {
94
+ const defaultRender = md.renderer.rules.image || function(tokens, idx, options, env, self) {
95
+ return self.renderToken(tokens, idx, options);
96
+ };
97
+
98
+ md.renderer.rules.image = function(tokens, idx, options, env, self) {
99
+ const token = tokens[idx];
100
+ const srcIndex = token.attrIndex('src');
101
+ if (srcIndex >= 0) {
102
+ const src = token.attrs[srcIndex][1];
103
+ const srcUrl = !isUrl(src) && resolveFileUrl(src, filePath)
104
+ if (srcUrl) token.attrs[srcIndex][1] = srcUrl;
105
+ }
106
+
107
+ return defaultRender(tokens, idx, options, env, self);
108
+ };
109
+ }
110
+ }
111
+
112
+ function replaceLinkPathsAndTitles(filePath) {
113
+
114
+ return function(md) {
115
+ const defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {
116
+ return self.renderToken(tokens, idx, options);
117
+ };
118
+
119
+ md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
120
+ const token = tokens[idx];
121
+ const srcIndex = token.attrIndex('href');
122
+ if (srcIndex >= 0) {
123
+ const src = token.attrs[srcIndex][1];
124
+ const srcUrl = !isUrl(src) && resolveFileUrl(src, filePath)
125
+ if (srcUrl) token.attrs[srcIndex][1] = srcUrl;
126
+ }
127
+
128
+ // set the title to the href if it's not already set
129
+ const titleIndex = token.attrIndex('title');
130
+ if (titleIndex < 0) {
131
+ const title = token.attrs[srcIndex][1];
132
+ token.attrPush(['title', title]);
133
+ }
134
+ return defaultRender(tokens, idx, options, env, self);
135
+ };
136
+ }
137
+ }
138
+
139
+ function isUrl(href) {
140
+ try {
141
+ new URL(href);
142
+ return true;
143
+ } catch (e) {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ function resolveFileUrl(src, filePath) {
149
+ const dirname = _platform === 'win32' ? path.win32.dirname : path.dirname;
150
+ const resolve = _platform === 'win32' ? path.win32.resolve : path.resolve;
151
+
152
+ if (src.startsWith('file://')) return src;
153
+ if (/^\.?\/$/.test(src)) src = './';
154
+ const srcDir = dirname(filePath);
155
+ const srcPath = resolve(srcDir, src) // filePath ? path.join(filePath, src) : path.resolve(src);
156
+ const srcUrl = pathToFileURL(srcPath);
157
+ if (src.endsWith('/')) return srcUrl + '/';
158
+
159
+ return srcUrl;
160
+
161
+ }
162
+
163
+ function pathToFileURL(absolutePath) {
164
+ if (_platform === 'win32') {
165
+ absolutePath = '/' + absolutePath.replace(/\\/g, '/');
166
+ }
167
+ return `file://${absolutePath}`;
168
+ }
169
+
170
+ function renderCancelledTasks(markdown) {
171
+ return markdown.replace(/- \[-\] (.+)/gm, '- :no_entry_sign: ~~$1~~');
172
+ }
173
+
174
+ export default {
175
+ extractWikilinkTopics,
176
+ renderMarkdown,
177
+ removeMD,
178
+ _setPlatform,
179
+ allEmoji
180
+ }
@@ -0,0 +1,12 @@
1
+ import { Chrono } from 'chrono-node'
2
+
3
+ export function replaceDateLanguage(text, asDate = false) {
4
+ const chrono = new Chrono();
5
+ return text.replace(/\(?\${(.*?)}\)?/g, (match, p1) => {
6
+ const date = chrono.parseDate(p1);
7
+ if (date) {
8
+ return asDate ? date : date.toISOString();
9
+ }
10
+ return match;
11
+ });
12
+ }