imdone-core 2.0.0 → 2.0.2

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 +178 -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 -7
  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,101 @@
1
+ import Plugin from 'imdone-api'
2
+ import { getIsoDateWithOffset } from '../adapters/date-time.js'
3
+ import path from 'path'
4
+
5
+ const simpleTemplate = '\n<!--\ncreated:${timestamp}\n-->\n'
6
+
7
+ const codeSpan = (value) => '`' + value + '`';
8
+ const codeBlock = (value, language = '') => '```' + language + '\n' + value + '\n```';
9
+
10
+ function flattenObject(object) {
11
+ return Object.keys(object).reduce((acc, key) => {
12
+ return { ...acc, [`template_${key}`]: object[key] };
13
+ }, {});
14
+ }
15
+
16
+ export default class DefaultBoardPropertiesPlugin extends Plugin {
17
+ constructor(project) {
18
+ super(project)
19
+ this.templates = {}
20
+ }
21
+
22
+ async onBeforeBoardUpdate() {
23
+ this.templates = await this.getTemplates()
24
+ }
25
+
26
+ async getBoardProperties() {
27
+ const templates = flattenObject(this.templates);
28
+ const clearFilterURL = this.getFilterURL('');
29
+ const getFilterURL = (filter) => this.getFilterURL(filter);
30
+ const getFilterLink = (filter) => `[Filter By: ${filter}](${this.getFilterURL(filter)})`;
31
+ // TODO Make board properties self documenting
32
+ // <!--
33
+ // #imdone-1.53.16
34
+ // #feature
35
+ // created:2025-02-14T16:54:22-05:00
36
+ // order:-185
37
+ // -->
38
+ // [Feature Description]
39
+ // ## :ballot_box_with_check: Tasks
40
+ // - [ ] Add tasks here
41
+ // ## :white_check_mark: DoD
42
+ // - [ ] Code complete (No Tech Debt)
43
+ // - [ ] Update tests
44
+ // - [ ] Automate what's working
45
+ // - [ ] Update tutorial project
46
+ // - [ ] Update documentation
47
+ // - [ ] Run like a new user and make the experience better
48
+ // - [ ] Make sure the first card is expanded by default or in view mode
49
+ // - [ ] Make sure global and default settings isn't modified when opened for the first time
50
+ return {
51
+ date: `${getIsoDateWithOffset(new Date()).substring(0, 10)}`,
52
+ timestamp: getIsoDateWithOffset(new Date()),
53
+ codeSpan,
54
+ codeBlock,
55
+ clearFilterURL,
56
+ getFilterURL,
57
+ getFilterLink,
58
+ ...templates
59
+ }
60
+ }
61
+
62
+ getFilterURL(filter) {
63
+ filter = filter.replace(/\(/g, '%28').replace(/\)/g, '%29');
64
+ return encodeURI(`imdone://${this.project.path}?filter=${filter}`)
65
+ }
66
+
67
+
68
+ async getTemplates() {
69
+ const { preparePathForWriting, readdir, readFile, stat, exists } = this.project.fileGateway;
70
+ const templateDir = path.join(this.project.configDir, 'templates');
71
+ const githubDir = path.join(this.project.path, '.github');
72
+
73
+ await preparePathForWriting(templateDir, true);
74
+
75
+ const readMarkdownFiles = async (dir, prefix = '') => {
76
+ const files = await readdir(dir);
77
+ const readFilePromises = files.map(async (file) => {
78
+ const filePath = path.join(dir, file);
79
+ const fileStat = await stat(filePath);
80
+ if (fileStat && fileStat.isDirectory()) {
81
+ return readMarkdownFiles(filePath, `${prefix}${file}_`);
82
+ } else if (file.endsWith('.md')) {
83
+ const data = await readFile(filePath, 'utf-8');
84
+ const templateName = `${prefix}${file.replace('.md', '').replace(/[^a-zA-Z0-9]/g, '_')}`;
85
+ return { [templateName]: data };
86
+ }
87
+ return {};
88
+ });
89
+
90
+ const allTemplates = await Promise.all(readFilePromises);
91
+ return allTemplates.reduce((acc, template) => ({ ...acc, ...template }), {});
92
+ };
93
+
94
+ const templatesFromTemplateDir = await readMarkdownFiles(templateDir);
95
+ const templatesFromGithubDir = await exists(githubDir)
96
+ ? await readMarkdownFiles(githubDir, 'github_')
97
+ : {};
98
+
99
+ return { simple: simpleTemplate, ...templatesFromTemplateDir, ...templatesFromGithubDir };
100
+ }
101
+ }
@@ -0,0 +1,7 @@
1
+ export * as ArchivePlugin from './archive-plugin.js'
2
+ export * as DefaultBoardActionsPlugin from './default-board-actions-plugin.js'
3
+ export * as DefaultBoardPropertiesPlugin from './default-board-properties-plugin.js'
4
+ export * as EpicPlugin from './epic-plugin.js'
5
+ export * as PersistTagsPlugin from './persist-tags-plugin.js'
6
+ export * as ExtensionPlugin from './extension-plugin.js'
7
+
@@ -0,0 +1,177 @@
1
+ import Plugin from 'imdone-api'
2
+ import { logger } from '../adapters/logger.js'
3
+
4
+ function getIsEpicMeta(task) {
5
+ return task.allMeta && task.allMeta['is-epic'] && task.allMeta['is-epic'][0]
6
+ }
7
+
8
+ function getEpicMeta(task) {
9
+ return task.allMeta && task.allMeta['epic'] && task.allMeta['epic'][0]
10
+ }
11
+
12
+ export default class EpicPlugin extends Plugin {
13
+ constructor(project) {
14
+ super(project)
15
+ this.epics = {}
16
+ }
17
+
18
+ async onBeforeBoardUpdate() {
19
+ this.epics = {}
20
+ }
21
+
22
+ onBoardUpdate(lists) {
23
+ // this html is getting initialized before onBoardUpdate
24
+ logger.log(
25
+ `${this.project.name} epics found:`,
26
+ Object.keys(this.epics).length
27
+ )
28
+ logger.time(`${this.project.name} Epic updates`)
29
+ this.updateCardMD(lists)
30
+ logger.timeEnd(`${this.project.name} Epic updates`)
31
+ }
32
+
33
+ onTaskUpdate(task) {
34
+ const isEpicMeta = getIsEpicMeta(task)
35
+ const epicMeta = getEpicMeta(task)
36
+ if (!isEpicMeta && !epicMeta) return
37
+
38
+ if (isEpicMeta && epicMeta) {
39
+ delete task.allMeta.epic
40
+ task.allMeta['is-epic'] = [epicMeta]
41
+ this.addEpic(epicMeta, task)
42
+ } else if (epicMeta) {
43
+ this.addEpicItem(epicMeta, task)
44
+ } else if (isEpicMeta) {
45
+ this.addEpic(isEpicMeta, task)
46
+ }
47
+ }
48
+
49
+ initEpic(name) {
50
+ if (!this.epics[name]) {
51
+ this.epics[name] = {
52
+ items: {},
53
+ }
54
+ }
55
+ }
56
+
57
+ addEpic(name, task) {
58
+ this.initEpic(name)
59
+ this.epics[name].epic = task
60
+ }
61
+
62
+ addEpicItem(name, task) {
63
+ const list = task.list
64
+ this.initEpic(name)
65
+ if (!this.getEpicItemsInList(name, list)) this.epics[name].items[list] = []
66
+ if (this.getEpicItemsInList(name, list).find((t) => t.id === task.id)) return
67
+ this.epics[name].items[list].push(task)
68
+ }
69
+
70
+ get epicNames() {
71
+ return Object.keys(this.epics)
72
+ }
73
+
74
+ getEpic(name) {
75
+ return this.epics[name].epic
76
+ }
77
+
78
+ getEpicItems(name) {
79
+ return this.epics[name].items
80
+ }
81
+
82
+ getEpicItemsInList(name, list) {
83
+ return this.epics[name].items[list]
84
+ }
85
+
86
+
87
+ getEpicLists(name) {
88
+ return Object.keys(this.getEpicItems(name))
89
+ }
90
+
91
+ isListHidden(name, lists) {
92
+ return lists.find((list) => list.name === name).hidden
93
+ }
94
+
95
+ isListIgnored(name, lists) {
96
+ return lists.find((list) => list.name === name).ignore
97
+ }
98
+
99
+ updateCardMD(lists) {
100
+ this.epicNames.forEach((epicId) => {
101
+ const epic = this.getEpic(epicId)
102
+ const epicLists = this.getEpicLists(epicId)
103
+ if (!epic) return
104
+ if (epicLists.length === 0) return
105
+ if (
106
+ epic.list === this.project.doneList &&
107
+ (this.isListHidden(epic.list, lists) ||
108
+ this.isListIgnored(epic.list, lists))
109
+ ) {
110
+ return
111
+ }
112
+
113
+ const epicText = epic.text // removeMD(epic.text)
114
+ const epicFilter = encodeURIComponent(
115
+ `(allMeta.epic="${epicId}" or allMeta.is-epic="${epicId}")`
116
+ )
117
+ const epicFilePath = encodeURI(
118
+ `${epic.source.repoId}/${epic.source.path}`.replace(/\\/g, '/')
119
+ )
120
+ const epicUrl = `imdone://${epicFilePath}?line=${epic.line}&filter=${epicFilter}`
121
+ const epicLink = `**Epic:** [${epicText}](${epicUrl})\n\n`
122
+
123
+ // Add epic link to epic tasks
124
+ epicLists.forEach((list) => {
125
+ this.getEpicItemsInList(epicId, list).forEach((task) => {
126
+ task.interpretedContent = epicLink + task.interpretedContent
127
+ task.init()
128
+ })
129
+ })
130
+
131
+ const taskList =
132
+ epicLists.length === 0
133
+ ? '*None*\n'
134
+ : '\n<div class="imdone-epic-items">\n\n' +
135
+ this.getTaskListMD(lists, epicFilter, epicUrl, epicId) +
136
+ '\n\n</div>\n'
137
+
138
+ if (epic.allMeta.epic && epic.allMeta.epic.includes(epicId)) {
139
+ const index = epic.allMeta.epic.findIndex((value) => value === epicId)
140
+ epic.allMeta.epic.splice(index, 1)
141
+ }
142
+ epic.interpretedContent =
143
+ epic.interpretedContent +
144
+ `\n----\n## [Epic items](${epicUrl})\n${taskList}`
145
+ epic.init()
146
+ })
147
+ }
148
+
149
+ getTaskListMD(lists, epicFilter, epicUrl, epicId) {
150
+ let content = ''
151
+
152
+ lists
153
+ .filter((list) => this.getEpicItemsInList(epicId, list.name))
154
+ .reverse()
155
+ .forEach((list) => {
156
+ const tasks = this.getEpicItemsInList(epicId, list.name)
157
+ const listUrl = epicUrl + encodeURIComponent(` and list=${list.name}`)
158
+ const listLink = `[${list.name}](${listUrl})`
159
+ content = content + '\n\n#### ' + listLink + '\n'
160
+ content += '<ul style="list-style: none;">'
161
+ content += tasks
162
+ .map((task) => {
163
+ const taskText = this.project.renderMarkdown(task.text, task.fullPath).replaceAll(/<p>|<\/p>/ig, '') // removeMD(task.text)
164
+ const checked =
165
+ task.list === this.project.doneList ? ' checked="true"' : ''
166
+ const taskFilePath = encodeURI(
167
+ `${task.source.repoId}/${task.source.path}`.replace(/\\/g, '/')
168
+ )
169
+ return `<li style="margin-left: 1rem; list-style: none;"><input type="checkbox" disabled="true"${checked}><a href="imdone://${taskFilePath}?line=${task.line}&filter=${epicFilter}" target="_blank">${taskText}</a></li>`
170
+ })
171
+ .join('\n')
172
+ content += '</ul>'
173
+ })
174
+
175
+ return content
176
+ }
177
+ }
@@ -0,0 +1,94 @@
1
+ import Plugin from 'imdone-api'
2
+ import _path from 'path'
3
+ import { exists } from '../adapters/file-gateway.js'
4
+ import { createRequire } from 'node:module';
5
+ import { logger } from '../adapters/logger.js';
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const url = new URL(import.meta.url)
9
+ const __dirname = _path.dirname(url.pathname)
10
+
11
+ export default class ExtensionPlugin extends Plugin {
12
+ constructor(project) {
13
+ super(project)
14
+ logger.log('loading extensions')
15
+ this.configDir = _path.join(project.path, '.imdone')
16
+ }
17
+
18
+ async init() {
19
+ this.cardActionsFunction = await this.loadExtensionModule(
20
+ () => [],
21
+ 'actions',
22
+ 'card'
23
+ )
24
+ this.boardActionsFunction = await this.loadExtensionModule(
25
+ () => [],
26
+ 'actions',
27
+ 'board'
28
+ )
29
+ this.cardPropertiesFunction = await this.loadExtensionModule(
30
+ () => {
31
+ return {}
32
+ },
33
+ 'properties',
34
+ 'card'
35
+ )
36
+ this.boardPropertiesFunction = await this.loadExtensionModule(
37
+ () => {
38
+ return {}
39
+ },
40
+ 'properties',
41
+ 'board'
42
+ )
43
+ }
44
+ getCardProperties(task) {
45
+ return this.cardPropertiesFunction(task)
46
+ }
47
+
48
+ async getBoardProperties() {
49
+ return await this.boardPropertiesFunction()
50
+ }
51
+
52
+ getCardActions(task) {
53
+ return this.cardActionsFunction(task)
54
+ }
55
+
56
+ getBoardActions() {
57
+ return this.boardActionsFunction().map(({ title, action, keys, icon }) => ({
58
+ title,
59
+ name: title,
60
+ action,
61
+ keys,
62
+ icon
63
+ }))
64
+ }
65
+
66
+ getConfigPath(relativePath) {
67
+ return _path.resolve(_path.join(this.configDir, ...relativePath) + '.js')
68
+ }
69
+
70
+ async loadExtensionModule(_default, ...path) {
71
+ let extension = _default
72
+ const extensionPath = this.getConfigPath(path)
73
+ try {
74
+ await exists(extensionPath)
75
+ delete require.cache[extensionPath]
76
+ extension = require(extensionPath)
77
+ } catch (e) {
78
+ if (e.code === 'ENOENT') {
79
+ logger.log('No extension found at:', extensionPath)
80
+ } else {
81
+ logger.warn('Error loading extension:', e.message)
82
+ }
83
+ }
84
+
85
+ if (typeof extension !== 'function') {
86
+ logger.warn(
87
+ `${extensionPath} does not export a function. Using default empty function. ${_default.toString()}`
88
+ )
89
+ extension = _default
90
+ }
91
+
92
+ return extension
93
+ }
94
+ }
@@ -0,0 +1,9 @@
1
+ export * from './archive-plugin.js';
2
+ export * from './default-board-actions-plugin.js';
3
+ export * from './default-board-properties-plugin.js';
4
+ export * from './default-plugins.js';
5
+ export * from './epic-plugin.js';
6
+ export * from './extension-plugin.js';
7
+ export * from './persist-tags-plugin.js';
8
+ export * from './plugin-manager.js';
9
+ export * from './plugin-registry.js';
@@ -0,0 +1,14 @@
1
+ import Plugin from 'imdone-api'
2
+ import { save } from '../adapters/storage/tags.js'
3
+
4
+ export default class PersistTagsPlugin extends Plugin {
5
+
6
+ constructor(project) {
7
+ super(project)
8
+ }
9
+
10
+ async onBoardUpdate() {
11
+ await save(this.project.allTags.sort(), this.project.path)
12
+ }
13
+
14
+ }