├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── CHANGELOG.md
├── README.md
├── build
└── .hotreload
├── manifest.json
├── package.json
├── rollup.config.js
├── src
├── hack-pdf.ts
├── md-it.d.ts
├── ob.d.ts
├── render.ts
├── settings.ts
└── tx-main.ts
├── tsconfig.json
└── versions.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | main.js
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: "@typescript-eslint/parser",
4 | parserOptions: {
5 | ecmaVersion: 2020,
6 | sourceType: "module",
7 | },
8 | extends: ["prettier", "plugin:prettier/recommended"],
9 | env: {
10 | browser: true,
11 | node: true,
12 | },
13 | plugins: [
14 | "@typescript-eslint",
15 | "jsdoc",
16 | "prefer-arrow",
17 | "simple-import-sort",
18 | ],
19 | rules: {
20 | "simple-import-sort/imports": "error",
21 | "simple-import-sort/exports": "error",
22 | "prefer-arrow/prefer-arrow-functions": [
23 | "warn",
24 | {
25 | disallowPrototype: true,
26 | singleReturnOnly: false,
27 | classPropertiesAllowed: false,
28 | },
29 | ],
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Intellij
2 | *.iml
3 | .idea
4 |
5 | # npm
6 | node_modules
7 | package-lock.json
8 |
9 | # build
10 | main.js
11 | *.js.map
12 | styles.css
13 | /build/*
14 | !build/.hotreload
15 |
16 | # saved config
17 | data.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": false,
3 | "tabWidth": 2,
4 | "trailingComma": "all",
5 | "overrides": [
6 | {
7 | "files": ".prettierrc",
8 | "options": {
9 | "parser": "json"
10 | }
11 | },
12 | {
13 | "files": "*.yml",
14 | "options": {
15 | "tabWidth": 2,
16 | "singleQuote": false
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [1.6.1](https://github.com/aidenlx/table-extended/compare/1.6.0...1.6.1) (2022-01-16)
2 |
3 |
4 | ### Bug Fixes
5 |
6 | * **hack-pdf:** fix internal links failed to export ([000472f](https://github.com/aidenlx/table-extended/commit/000472fd9e5dbe73fed6262add7f51137eed5404))
7 |
8 | # [1.6.0](https://github.com/aidenlx/table-extended/compare/1.5.1...1.6.0) (2022-01-16)
9 |
10 |
11 | ### Bug Fixes
12 |
13 | * fix footnote refs and contents rendered within table cells in -tx- tables ([186eb41](https://github.com/aidenlx/table-extended/commit/186eb41119708166964bf9e1a66a75bb943882c1))
14 |
15 |
16 | ### Features
17 |
18 | * experimental support for export to PDF; improve multiline table handling ([9a4faf2](https://github.com/aidenlx/table-extended/commit/9a4faf227614f5c7fe04472000896bd5d514b91f))
19 |
20 | ## [1.5.1](https://github.com/aidenlx/table-extended/compare/1.5.0...1.5.1) (2022-01-13)
21 |
22 |
23 | ### Bug Fixes
24 |
25 | * fix markdown-attributes compatibility ([5ab62d7](https://github.com/aidenlx/table-extended/commit/5ab62d7bbf3d224462cb960e96e7df482dd26c9c)), closes [#26](https://github.com/aidenlx/table-extended/issues/26)
26 |
27 | # [1.5.0](https://github.com/aidenlx/table-extended/compare/1.4.1...1.5.0) (2022-01-12)
28 |
29 |
30 | ### Bug Fixes
31 |
32 | * fix html not rendered; fix broken multiline mode ([92f46bd](https://github.com/aidenlx/table-extended/commit/92f46bd394fccf6ec3768d77232b345689498c48)), closes [#23](https://github.com/aidenlx/table-extended/issues/23)
33 |
34 |
35 | ### Features
36 |
37 | * support -tx- table in blockquote ([d4dfb1f](https://github.com/aidenlx/table-extended/commit/d4dfb1f452530b195f71465ec8a8f3986bbd5bdb)), closes [#28](https://github.com/aidenlx/table-extended/issues/28)
38 |
39 | ## [1.4.1](https://github.com/aidenlx/table-extended/compare/1.4.0...1.4.1) (2021-11-29)
40 |
41 |
42 | ### Bug Fixes
43 |
44 | * remove desktop only flag ([3c153cf](https://github.com/aidenlx/table-extended/commit/3c153cfa1258d1c651b44049bd8416ab2086fe7b)), closes [#9](https://github.com/aidenlx/table-extended/issues/9)
45 |
46 | # [1.4.0](https://github.com/aidenlx/table-extended/compare/1.3.2...1.4.0) (2021-10-25)
47 |
48 |
49 | ### Features
50 |
51 | * add support to footnote and math embeds; fix embeds not loaded ([c6fea5a](https://github.com/aidenlx/table-extended/commit/c6fea5a94c1c743ef95e38e0d4f79ede4352a401)), closes [#5](https://github.com/aidenlx/table-extended/issues/5) [#22](https://github.com/aidenlx/table-extended/issues/22) [#6](https://github.com/aidenlx/table-extended/issues/6) [#11](https://github.com/aidenlx/table-extended/issues/11)
52 |
53 | ## [1.3.2](https://github.com/aidenlx/table-extended/compare/1.3.1...1.3.2) (2021-10-07)
54 |
55 |
56 | ### Bug Fixes
57 |
58 | * fix prefix not detected when strict line break is disabled ([38f27ca](https://github.com/aidenlx/table-extended/commit/38f27ca8ebc2fb44cc671b09fd7217848f8cff91)), closes [#20](https://github.com/aidenlx/table-extended/issues/20)
59 |
60 | ## [1.3.1](https://github.com/aidenlx/table-extended/compare/1.3.0...1.3.1) (2021-10-02)
61 |
62 |
63 | ### Bug Fixes
64 |
65 | * fix failed to get raw markdown text; fix release script ([4584be8](https://github.com/aidenlx/table-extended/commit/4584be839430fd80d5e7664c91cb914bf725d71e)), closes [#13](https://github.com/aidenlx/table-extended/issues/13) [#8](https://github.com/aidenlx/table-extended/issues/8)
66 |
67 | # [1.3.0](https://github.com/alx-plugins/table-extended/compare/1.2.0...1.3.0) (2021-06-23)
68 |
69 |
70 | ### Features
71 |
72 | * add `-tx-` prefix indicator, mark extended native syntax expermental ([d4efa3f](https://github.com/alx-plugins/table-extended/commit/d4efa3f3fe5397c6a33ee09a0bb1c9f28f34f344))
73 |
74 | # [1.2.0](https://github.com/alx-plugins/table-extended/compare/1.1.1...1.2.0) (2021-06-23)
75 |
76 |
77 | ### Features
78 |
79 | * **settings.ts:** add option to disable extended native syntax ([6042c75](https://github.com/alx-plugins/table-extended/commit/6042c75332d75c68b5cb901f84f4a49e14ad3cbf))
80 | * support codeblock-free mmd table syntax ([9d823dc](https://github.com/alx-plugins/table-extended/commit/9d823dc6361f8d3efa06a6a2d699dc179c815af1)), closes [#4](https://github.com/alx-plugins/table-extended/issues/4) [#2](https://github.com/alx-plugins/table-extended/issues/2)
81 |
82 | ## [1.1.1](https://github.com/alx-plugins/table-extended/compare/1.1.0...1.1.1) (2021-06-03)
83 |
84 |
85 | ### Bug Fixes
86 |
87 | * fix internal embeds not parsed properly ([ef09fd2](https://github.com/alx-plugins/table-extended/commit/ef09fd281cbdc22beb864e1ab7b7a27d65a78830))
88 |
89 | # [1.1.0](https://github.com/alx-plugins/table-extended/compare/1.0.1...1.1.0) (2021-05-03)
90 |
91 |
92 | ### Features
93 |
94 | * add mark and raw html support in tx ([a45a73d](https://github.com/alx-plugins/table-extended/commit/a45a73d0f13c5c2e31d5dc605fce4473036f05da)), closes [#1](https://github.com/alx-plugins/table-extended/issues/1)
95 |
96 |
97 |
98 | ## [1.0.1](https://github.com/alx-plugins/table-extended/compare/1.0.1...1.1.0) (2021-04-06)
99 |
100 |
101 |
102 | ## [1.0.1](https://github.com/alx-plugins/table-extended/compare/1.0.1...1.1.0) (2021-04-06)
103 |
104 |
105 |
106 | # [1.0.0](https://github.com/alx-plugins/table-extended/compare/1.0.1...1.1.0) (2021-04-03)
107 |
108 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Table Extended
2 |
3 | Extend basic table in Obsidian with MultiMarkdown table syntax.
4 |
5 | 
6 |
7 | - [Table Extended](#table-extended)
8 | - [Intro](#intro)
9 | - [Known issue](#known-issue)
10 | - [How to use](#how-to-use)
11 | - [Expermental: Extended Native Syntax](#expermental-extended-native-syntax)
12 | - [Multiline](#multiline)
13 | - [Multiline Header](#multiline-header)
14 | - [Headerless](#headerless)
15 | - [Compatibility](#compatibility)
16 | - [Installation](#installation)
17 | - [From Obsidian](#from-obsidian)
18 | - [From GitHub](#from-github)
19 | - [Behind the scene](#behind-the-scene)
20 |
21 | ## Intro
22 |
23 | Obsidian's [built-in table syntax](https://help.obsidian.md/How+to/Format+your+notes#tables) can only define the basics for tables. When users try to apply complex tables with `colspan` or multiple headers, their only option is to fall back to raw HTML, which is difficult to read and edit.
24 |
25 | This plugin brings [MultiMarkdown table syntax][mmd6-table] to Obsidian, which provides the following features with internal links and embeds intact:
26 |
27 | - [Cell spans over columns](#colspan)
28 | - [Cell spans over rows](#rowspan)
29 | - [Block-level elements](#multiline) such as lists, codes...
30 | - [Multiple table headers](#multiline-header)
31 | - Table caption
32 | - [Omitted table header](#headerless)
33 |
34 | [mmd6]: https://fletcher.github.io/MultiMarkdown-6/
35 | [mdit]: https://markdown-it.github.io/
36 | [mmdt]: https://github.com/RedBug312/markdown-it-multimd-table
37 |
38 | ## Known issue
39 |
40 | - This plugin is not yet compatible with [Advanced Tables](https://github.com/tgrosinger/advanced-tables-obsidian), as its auto-formatting would break the mmd6 table syntax.
41 | - Related issue: [advanced-tables-obsidian #59](https://github.com/tgrosinger/advanced-tables-obsidian/issues/59#issuecomment-812886995)
42 | - table with `-tx-` may sometimes ignore escape characters, for example, `\|` fails to escape `|` in table, only `\\|` works
43 | - extended native syntax may not work sometimes, with console output: `failed to get Markdown text, escaping...`
44 |
45 | ## How to use
46 |
47 | The latest version use a new syntax to indicate extended tables in favor of fenced `tx` code blocks, which allow better support for backlinks and forward links, which use a leading `-tx-` before table:
48 |
49 | PS: For expermental extended native syntax support which eliminate the need for `-tx-` prefix, check [here](#expermental-extended-native-syntax)
50 |
51 | ```md
52 |
53 | -tx-
54 | | | Grouping ||
55 | First Header | Second Header | Third Header |
56 | ------------ | :-----------: | -----------: |
57 | Content | *Long Cell* ||
58 | Content | **Cell** | Cell |
59 | New section | More | Data |
60 | And more | With an escaped '\|' ||
61 | [Prototype table]
62 |
63 | ```
64 |
65 | would be render as:
66 |
67 |
68 | Prototype table
69 |
70 |
71 | |
72 | Grouping |
73 |
74 |
75 | First Header |
76 | Second Header |
77 | Third Header |
78 |
79 |
80 |
81 |
82 | Content |
83 | Long Cell |
84 |
85 |
86 | Content |
87 | Cell |
88 | Cell |
89 |
90 |
91 |
92 |
93 | New section |
94 | More |
95 | Data |
96 |
97 |
98 | And more |
99 | With an escaped '|' |
100 |
101 |
102 |
103 |
104 |
105 | For more detailed guide, check [markdown-it-multimd-table README][mmdtg] and [MultiMarkdown User's Guide][mmd6-table]
106 |
107 | [mmdtg]: https://github.com/RedBug312/markdown-it-multimd-table/blob/master/README.md#usage
108 | [mmd6-table]: https://fletcher.github.io/MultiMarkdown-6/syntax/tables.html
109 |
110 | ### Expermental: Extended Native Syntax
111 |
112 | Note: the following features are not supported:
113 |
114 | - [Multiple table headers](#multiline-header)
115 | - Table caption
116 | - [Omitted table header](#headerless)
117 |
118 | Extended syntax is allowed in Obsidian's regular tables when option is enabled is the setting tab:
119 |
120 | The following table:
121 |
122 | ```md
123 | First Header | Second Header | Third Header |
124 | ------------ | :-----------: | -----------: |
125 | Content | *Long Cell* ||
126 | Content | **Cell** | Cell |
127 | New section | More | Data |
128 | And more | With an escaped '\|' ||
129 | ```
130 |
131 | would be render as:
132 |
133 |
134 |
135 |
136 | First Header |
137 | Second Header |
138 | Third Header |
139 |
140 |
141 |
142 |
143 | Content |
144 | Long Cell |
145 |
146 |
147 | Content |
148 | Cell |
149 | Cell |
150 |
151 |
152 |
153 |
154 | New section |
155 | More |
156 | Data |
157 |
158 |
159 | And more |
160 | With an escaped '|' |
161 |
162 |
163 |
164 |
165 |
166 | ### Multiline
167 |
168 | Backslash at end merges with line content below.
169 |
170 | ```markdown
171 | | Markdown | Rendered HTML |
172 | |--------------|---------------|
173 | | *Italic* | *Italic* | \
174 | | | |
175 | | - Item 1 | - Item 1 | \
176 | | - Item 2 | - Item 2 |
177 | | ```python | ```python \
178 | | .1 + .2 | .1 + .2 \
179 | | ``` | ``` |
180 | ```
181 |
182 | This is parsed below:
183 |
184 |
185 |
186 |
187 | Markdown |
188 | Rendered HTML |
189 |
190 |
191 |
192 |
193 |
194 | *Italic*
195 |
196 | |
197 |
198 | Italic
199 | |
200 |
201 |
202 |
203 | - Item 1
204 | - Item 2
205 | |
206 |
207 |
208 | - Item 1
209 | - Item 2
210 |
211 | |
212 |
213 |
214 |
215 | ```python
216 | .1 + .2
217 | ```
218 | |
219 |
220 | .1 + .2
221 |
222 | |
223 |
224 |
225 |
226 |
227 | ### Rowspan
228 |
229 | `^^` indicates cells being merged above.
230 |
231 | ```markdown
232 | Stage | Direct Products | ATP Yields
233 | ----: | --------------: | ---------:
234 | Glycolysis | 2 ATP ||
235 | ^^ | 2 NADH | 3--5 ATP |
236 | Pyruvaye oxidation | 2 NADH | 5 ATP |
237 | Citric acid cycle | 2 ATP ||
238 | ^^ | 6 NADH | 15 ATP |
239 | ^^ | 2 FADH2 | 3 ATP |
240 | **30--32** ATP |||
241 | ```
242 |
243 | This is parsed below:
244 |
245 |
246 |
247 |
248 | Stage |
249 | Direct Products |
250 | ATP Yields |
251 |
252 |
253 |
254 |
255 | Glycolysis |
256 | 2 ATP |
257 |
258 |
259 | 2 NADH |
260 | 3–5 ATP |
261 |
262 |
263 | Pyruvaye oxidation |
264 | 2 NADH |
265 | 5 ATP |
266 |
267 |
268 | Citric acid cycle |
269 | 2 ATP |
270 |
271 |
272 | 6 NADH |
273 | 15 ATP |
274 |
275 |
276 | 2 FADH2 |
277 | 3 ATP |
278 |
279 |
280 | 30–32 ATP |
281 |
282 |
283 |
284 |
285 | ### Multiline Header
286 |
287 | ```tx
288 | | | Grouping ||
289 | First Header | Second Header | Third Header |
290 | ------------ | :-----------: | -----------: |
291 | Content | *Long Cell* ||
292 | ```
293 |
294 | rendered as:
295 |
296 |
297 |
298 | |
299 | Grouping |
300 |
301 |
302 | First Header |
303 | Second Header |
304 | Third Header |
305 |
306 |
307 |
308 |
309 | Content |
310 | Long Cell |
311 |
312 |
313 |
314 |
315 |
316 | ### Headerless
317 |
318 | Table header can be eliminated.
319 |
320 | ```markdown
321 | |--|--|--|--|--|--|--|--|
322 | |♜| |♝|♛|♚|♝|♞|♜|
323 | | |♟|♟|♟| |♟|♟|♟|
324 | |♟| |♞| | | | | |
325 | | |♗| | |♟| | | |
326 | | | | | |♙| | | |
327 | | | | | | |♘| | |
328 | |♙|♙|♙|♙| |♙|♙|♙|
329 | |♖|♘|♗|♕|♔| | |♖|
330 | ```
331 |
332 | This is parsed below:
333 |
334 |
335 |
336 |
337 | ♜ |
338 | |
339 | ♝ |
340 | ♛ |
341 | ♚ |
342 | ♝ |
343 | ♞ |
344 | ♜ |
345 |
346 |
347 | |
348 | ♟ |
349 | ♟ |
350 | ♟ |
351 | |
352 | ♟ |
353 | ♟ |
354 | ♟ |
355 |
356 |
357 | ♟ |
358 | |
359 | ♞ |
360 | |
361 | |
362 | |
363 | |
364 | |
365 |
366 |
367 | |
368 | ♗ |
369 | |
370 | |
371 | ♟ |
372 | |
373 | |
374 | |
375 |
376 |
377 | |
378 | |
379 | |
380 | |
381 | ♙ |
382 | |
383 | |
384 | |
385 |
386 |
387 | |
388 | |
389 | |
390 | |
391 | |
392 | ♘ |
393 | |
394 | |
395 |
396 |
397 | ♙ |
398 | ♙ |
399 | ♙ |
400 | ♙ |
401 | |
402 | ♙ |
403 | ♙ |
404 | ♙ |
405 |
406 |
407 | ♖ |
408 | ♘ |
409 | ♗ |
410 | ♕ |
411 | ♔ |
412 | |
413 | |
414 | ♖ |
415 |
416 |
417 |
418 |
419 | ## Compatibility
420 |
421 | The required API feature is only available for Obsidian v0.12.0+.
422 |
423 | ## Installation
424 |
425 | ### From Obsidian
426 |
427 | 1. Open `Settings` > `Third-party plugin`
428 | 2. Make sure Safe mode is **off**
429 | 3. Click `Browse community plugins`
430 | 4. Search for this plugin
431 | 5. Click `Install`
432 | 6. Once installed, close the community plugins window and the patch is ready to use.
433 |
434 | ### From GitHub
435 |
436 | 1. Download the Latest Release from the Releases section of the GitHub Repository
437 | 2. Put files to your vault's plugins folder: `/.obsidian/plugins/table-extended`
438 | 3. Reload Obsidian
439 | 4. If prompted about Safe Mode, you can disable safe mode and enable the plugin.
440 | Otherwise, head to Settings, third-party plugins, make sure safe mode is off and
441 | enable the plugin from there.
442 |
443 | > Note: The `.obsidian` folder may be hidden. On macOS, you should be able to press `Command+Shift+Dot` to show the folder in Finder.
444 |
445 | ## Behind the scene
446 |
447 | Due to the restriction of the current Obsidian API, the built-in markdown parser is not configurable. Instead, This plugin includes an standalone Markdown parser [markdown-it][mdit] with plugin[markdown-it-multimd-table][mmdt], and table sections and the texts inside code block with language tag `tx` are passed to `markdown-it`. The internal links and embeds, however, are extracted and passed to Obsidian, so the core features of obsidian remain intact.
448 |
449 | Noted that the plugin may behave differently from the official MultiMarkdown compiler and Obsidian's parser, Please pose an issue if there are unexpected results for sensible inputs.
450 |
--------------------------------------------------------------------------------
/build/.hotreload:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aidenlx/table-extended/7aa048c2ba8ca6152f4170b291ddaf7fcd2ed3f3/build/.hotreload
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "table-extended",
3 | "name": "Table Extended",
4 | "version": "1.6.1",
5 | "minAppVersion": "0.12.0",
6 | "description": "Enable extended table support with MultiMarkdown 6 syntax",
7 | "author": "AidenLx",
8 | "authorUrl": "https://github.com/AidenLx/",
9 | "isDesktopOnly": false
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "table-extended",
3 | "version": "1.6.1",
4 | "description": "Enable extended table support with MultiMarkdown 6 syntax",
5 | "main": "main.js",
6 | "scripts": {
7 | "dev": "rollup --config rollup.config.js -w",
8 | "build": "rollup --config rollup.config.js --environment BUILD:production",
9 | "prettier": "prettier --write 'src/**/*.+(ts|tsx|json|html|css)'",
10 | "eslint": "eslint . --ext .ts,.tsx --fix",
11 | "release": "release-it"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "MIT",
16 | "devDependencies": {
17 | "@gerhobbelt/markdown-it-regexp": "^0.6.0-15",
18 | "@release-it/bumper": "^3.0.1",
19 | "@release-it/conventional-changelog": "^4.1.0",
20 | "@rollup/plugin-commonjs": "^21.0.1",
21 | "@rollup/plugin-json": "^4.1.0",
22 | "@rollup/plugin-node-resolve": "^13.1.3",
23 | "@rollup/plugin-typescript": "^8.3.0",
24 | "@types/json-schema": "^7.0.9",
25 | "@types/markdown-it": "^12.2.3",
26 | "@types/node": "^17.0.8",
27 | "@typescript-eslint/eslint-plugin": "^5.9.1",
28 | "@typescript-eslint/parser": "^5.9.1",
29 | "cz-conventional-changelog": "^3.3.0",
30 | "eslint": "^8.6.0",
31 | "eslint-config-prettier": "^8.3.0",
32 | "eslint-plugin-jsdoc": "^37.6.1",
33 | "eslint-plugin-prefer-arrow": "^1.2.3",
34 | "eslint-plugin-prettier": "^4.0.0",
35 | "eslint-plugin-simple-import-sort": "^7.0.0",
36 | "json": "^11.0.0",
37 | "markdown-it": "^12.3.2",
38 | "markdown-it-footnote": "^3.0.3",
39 | "markdown-it-mark": "^3.0.1",
40 | "markdown-it-multimd-table": "^4.1.1",
41 | "monkey-around": "^2.3.0",
42 | "obsidian": "^0.13.11",
43 | "prettier": "^2.5.1",
44 | "punycode": "^2.1.1",
45 | "release-it": "^14.12.1",
46 | "rollup": "^2.63.0",
47 | "rollup-plugin-copy": "^3.4.0",
48 | "rollup-plugin-import-css": "^3.0.2",
49 | "tslib": "^2.3.1",
50 | "typescript": "^4.5.4"
51 | },
52 | "release-it": {
53 | "hooks": {
54 | "before:init": [
55 | "npm run prettier",
56 | "npm run eslint"
57 | ],
58 | "after:bump": [
59 | "json -I -f manifest.json -e \"this.version='${version}'\"",
60 | "json -I -f versions.json -e \"this['${version}']='$(cat manifest.json | json minAppVersion)'\"",
61 | "sed -i '' \"s/available for Obsidian v.*$/available for Obsidian v$(cat manifest.json | json minAppVersion)+./\" README.md",
62 | "git add .",
63 | "npm run build"
64 | ],
65 | "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
66 | },
67 | "git": {
68 | "commitMessage": "chore: release v${version}",
69 | "tagName": "${version}",
70 | "tagAnnotation": "Release v${version}"
71 | },
72 | "npm": {
73 | "publish": false
74 | },
75 | "github": {
76 | "release": true,
77 | "assets": [
78 | "build/main.js",
79 | "build/manifest.json"
80 | ],
81 | "proxy": "http://127.0.0.1:7890",
82 | "releaseName": "${version}"
83 | },
84 | "plugins": {
85 | "@release-it/bumper": {
86 | "out": "manifest.json"
87 | },
88 | "@release-it/conventional-changelog": {
89 | "preset": "angular",
90 | "infile": "CHANGELOG.md"
91 | }
92 | }
93 | },
94 | "config": {
95 | "commitizen": {
96 | "path": "./node_modules/cz-conventional-changelog"
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from "@rollup/plugin-commonjs";
2 | import json from "@rollup/plugin-json";
3 | import { nodeResolve } from "@rollup/plugin-node-resolve";
4 | import typescript from "@rollup/plugin-typescript";
5 | import copy from "rollup-plugin-copy";
6 |
7 | const isProd = process.env.BUILD === "production";
8 |
9 | const banner = `/*
10 | THIS IS A GENERATED/BUNDLED FILE BY ROLLUP
11 | if you want to view the source visit the plugins github repository
12 | */
13 | `;
14 |
15 | export default {
16 | input: "src/tx-main.ts",
17 | output: {
18 | file: "build/main.js",
19 | sourcemap: "inline",
20 | sourcemapExcludeSources: isProd,
21 | format: "cjs",
22 | exports: "default",
23 | banner,
24 | },
25 | external: ["obsidian"],
26 | plugins: [
27 | typescript(),
28 | nodeResolve({ browser: true }),
29 | commonjs(),
30 | json(),
31 | copy({
32 | targets: [{ src: "manifest.json", dest: "build" }],
33 | }),
34 | ],
35 | };
36 |
--------------------------------------------------------------------------------
/src/hack-pdf.ts:
--------------------------------------------------------------------------------
1 | import { around } from "monkey-around";
2 | import { MarkdownView, TFile, Vault } from "obsidian";
3 |
4 | import TableExtended from "./tx-main";
5 |
6 | const Export2PDFHack = (plugin: TableExtended) => {
7 | const unloaders = [
8 | around(MarkdownView.prototype, {
9 | // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
10 | printToPdf: (original) =>
11 | function (this: MarkdownView) {
12 | plugin.print2pdfFileCache = this.file;
13 | // shalow copy the file to provide basic info
14 | this.file = { ...this.file, export2pdf: true } as any;
15 | original.call(this);
16 | this.file = plugin.print2pdfFileCache;
17 | },
18 | }),
19 | around(Vault.prototype, {
20 | cachedRead: (original) =>
21 | async function (this: Vault, input: TFile | string) {
22 | if (!(input instanceof TFile) && (input as any)?.export2pdf) {
23 | const file = plugin.print2pdfFileCache;
24 | if (!file) {
25 | throw new Error(
26 | "Failed to get file from table extended plugin instance",
27 | );
28 | }
29 | return preprocessMarkdown(await original.call(this, file), plugin);
30 | } else {
31 | return original.call(this, input as any);
32 | }
33 | },
34 | }),
35 | ];
36 | unloaders.forEach((u) => plugin.register(u));
37 | };
38 |
39 | /**
40 | * warp all tables in markdown text with tx codeblock
41 | */
42 | const preprocessMarkdown = (text: string, plugin: TableExtended) => {
43 | if (!text) return text;
44 | const ast = plugin.mdit.parse(text, {});
45 | let tableStarts: number[] = [],
46 | tableEnds: number[] = [];
47 | let linesToRemove: number[] = [];
48 |
49 | ast.forEach((token, index, allTokens) => {
50 | if (token.type === "table_open") {
51 | let txTable = false;
52 | if (index - 3 >= 0) {
53 | const paraStart = index - 3,
54 | paraContent = index - 2;
55 | if (
56 | allTokens[paraStart].type === "paragraph_open" &&
57 | allTokens[paraContent].type === "inline" &&
58 | allTokens[paraContent].content === "-tx-"
59 | ) {
60 | // remove -tx- prefix
61 | linesToRemove.push(token.map![0] - 1);
62 | txTable = true;
63 | }
64 | }
65 | // process all tables or only tables with -tx- prefix
66 | if (plugin.settings.handleNativeTable || txTable) {
67 | tableStarts.push(token.map![0]);
68 | tableEnds.push(token.map![1]);
69 | }
70 | }
71 | });
72 |
73 | if (tableStarts.length === 0) return text;
74 |
75 | let lines = text.split(/\r?\n/).flatMap((line, index) => {
76 | // remove -tx- prefix
77 | if (linesToRemove.includes(index)) return [];
78 | // warp all tables with tx codeblock
79 | if (tableStarts.includes(index)) return ["```tx", line];
80 | if (tableEnds.includes(index)) return ["```", line];
81 | return [line];
82 | });
83 | return lines.join("\n");
84 | };
85 |
86 | export default Export2PDFHack;
87 |
--------------------------------------------------------------------------------
/src/md-it.d.ts:
--------------------------------------------------------------------------------
1 | declare module "markdown-it-multimd-table";
2 | // declare module 'markdown-it';
3 | declare module "@gerhobbelt/markdown-it-regexp";
4 | declare module "markdown-it-footnote";
5 | declare module "markdown-it-mark";
6 |
--------------------------------------------------------------------------------
/src/ob.d.ts:
--------------------------------------------------------------------------------
1 | import "obsidian";
2 |
3 | declare module "obsidian" {
4 | interface Vault {
5 | getConfig(key: string): unknown;
6 | }
7 | interface MarkdownView {
8 | printToPdf(): void;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/render.ts:
--------------------------------------------------------------------------------
1 | import type Token from "markdown-it/lib/token";
2 | import {
3 | MarkdownPostProcessorContext,
4 | MarkdownRenderChild,
5 | MarkdownRenderer,
6 | } from "obsidian";
7 |
8 | import TableExtended from "./tx-main";
9 |
10 | export const mditOptions = { html: true };
11 |
12 | const elToPreserveText = ["td", "th", "caption"];
13 | /**
14 | * process ast to extract source markdown text from table cells
15 | * @param ast ids are added for each table cell
16 | * @returns array of markdown text, with index being part of id for corresponding cell element
17 | */
18 | const processAST = (ast: Token[]): string[] => {
19 | let srcMarkdown: string[] = [];
20 |
21 | for (let i = 0; i < ast.length; i++) {
22 | const token = ast[i];
23 | if (elToPreserveText.includes(token.tag) && token.nesting === 1) {
24 | let iInline = i,
25 | nextToken = ast[++iInline];
26 | while (
27 | // not closing tag
28 | !elToPreserveText.includes(nextToken.tag) ||
29 | nextToken.nesting !== -1
30 | ) {
31 | let content = "";
32 | if (nextToken.type === "inline") {
33 | content = nextToken.content;
34 | } else if (nextToken.type === "fence") {
35 | content =
36 | "```" + nextToken.info + "\n" + nextToken.content + "\n" + "```";
37 | } else if (nextToken.type === "code_block") {
38 | content = nextToken.content.replace(/^/gm, " ");
39 | }
40 |
41 | if (content) {
42 | const index = srcMarkdown.push(content) - 1;
43 | token.attrSet("id", `TX_${index}`);
44 | break;
45 | }
46 | nextToken = ast[++iInline];
47 | }
48 | // skip inline token and close token
49 | i = iInline;
50 | }
51 | }
52 | return srcMarkdown;
53 | };
54 |
55 | // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
56 | export function renderMarkdown(
57 | this: TableExtended,
58 | src: string,
59 | blockEl: HTMLElement,
60 | ctx: MarkdownPostProcessorContext,
61 | ) {
62 | let child = new MarkdownRenderChild(blockEl);
63 | ctx.addChild(child);
64 | // import render results
65 | const ast = this.mdit.parse(src, {}),
66 | MarkdownTextInTable = processAST(ast);
67 |
68 | const result = this.mdit.renderer.render(ast, mditOptions, {});
69 | blockEl.innerHTML = result;
70 | for (let el of blockEl.querySelectorAll("[id^=TX_]")) {
71 | const parent = el as HTMLElement,
72 | indexText = el.id.substring(3 /* "TX_".length */);
73 | el.removeAttribute("id");
74 | if (!Number.isInteger(+indexText)) continue;
75 | const text = MarkdownTextInTable[+indexText];
76 | if (!text) continue;
77 | parent.empty();
78 | MarkdownRenderer.renderMarkdown(text, parent, ctx.sourcePath, child);
79 |
80 | let renderedFirstBlock = parent.firstElementChild;
81 | if (renderedFirstBlock) {
82 | const from = renderedFirstBlock;
83 | // copy attr set in markdown-attribute
84 | ["style", "class", "id"].forEach((attr) => copyAttr(attr, from, parent));
85 | if (renderedFirstBlock instanceof HTMLElement) {
86 | Object.assign(parent.dataset, renderedFirstBlock.dataset);
87 | }
88 | // unwarp all children to the parent table cell/caption
89 | if (renderedFirstBlock instanceof HTMLParagraphElement)
90 | renderedFirstBlock.replaceWith(...renderedFirstBlock.childNodes);
91 | }
92 | }
93 | }
94 |
95 | const copyAttr = (attr: string, from: Element, to: Element) => {
96 | if (from.hasAttribute(attr)) {
97 | to.setAttribute(attr, from.getAttribute(attr)!);
98 | }
99 | };
100 |
--------------------------------------------------------------------------------
/src/settings.ts:
--------------------------------------------------------------------------------
1 | import {
2 | App,
3 | MarkdownPreviewRenderer,
4 | PluginSettingTab,
5 | Setting,
6 | } from "obsidian";
7 | import TableExtended from "tx-main";
8 |
9 | export interface TableExtendedSettings {
10 | handleNativeTable: boolean;
11 | hackPDF: boolean;
12 | }
13 |
14 | export const DEFAULT_SETTINGS: TableExtendedSettings = {
15 | handleNativeTable: false,
16 | hackPDF: false,
17 | };
18 |
19 | export class TableExtendedSettingTab extends PluginSettingTab {
20 | plugin: TableExtended;
21 |
22 | constructor(app: App, plugin: TableExtended) {
23 | super(app, plugin);
24 | this.plugin = plugin;
25 | }
26 |
27 | display(): void {
28 | this.containerEl.empty();
29 | new Setting(this.containerEl)
30 | .setName("Expermental: Extended Native Table Syntax")
31 | .setDesc(
32 | createFragment((descEl) => {
33 | descEl.appendText("Use extended syntax on all native tables");
34 | descEl.appendChild(createEl("br"));
35 | descEl.appendText(
36 | "Some table features may be broken, if found any, please open new issue in ",
37 | );
38 | descEl.appendChild(
39 | createEl("a", {
40 | text: "here",
41 | attr: {
42 | href: "https://github.com/alx-plugins/table-extended/issues",
43 | },
44 | }),
45 | );
46 | }),
47 | )
48 | .addToggle((toggle) =>
49 | toggle
50 | .setValue(this.plugin.settings.handleNativeTable)
51 | .onChange(async (value) => {
52 | this.plugin.settings.handleNativeTable = value;
53 | if (value)
54 | MarkdownPreviewRenderer.registerPostProcessor(
55 | this.plugin.processNativeTable,
56 | );
57 | else
58 | MarkdownPreviewRenderer.unregisterPostProcessor(
59 | this.plugin.processNativeTable,
60 | );
61 | this.plugin.refresh();
62 | this.plugin.saveData(this.plugin.settings);
63 | this.display();
64 | }),
65 | );
66 | new Setting(this.containerEl)
67 | .setName("Expermental: Export to PDF support")
68 | .setDesc(
69 | createFragment((descEl) => {
70 | descEl.appendText("Reload obsidian to take effect");
71 | descEl.appendChild(createEl("br"));
72 | descEl.appendText(
73 | "If PDF export is broken with this option enabled, disable this feature and open new issue in ",
74 | );
75 | descEl.appendChild(
76 | createEl("a", {
77 | text: "here",
78 | attr: {
79 | href: "https://github.com/alx-plugins/table-extended/issues",
80 | },
81 | }),
82 | );
83 | }),
84 | )
85 | .addToggle((toggle) =>
86 | toggle
87 | .setValue(this.plugin.settings.hackPDF)
88 | .onChange(async (value) => {
89 | this.plugin.settings.hackPDF = value;
90 | this.plugin.saveData(this.plugin.settings);
91 | this.display();
92 | }),
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/tx-main.ts:
--------------------------------------------------------------------------------
1 | import MarkdownIt from "markdown-it";
2 | import mTable from "markdown-it-multimd-table";
3 | import {
4 | MarkdownPostProcessorContext,
5 | MarkdownPreviewRenderer,
6 | MarkdownView,
7 | Plugin,
8 | TFile,
9 | } from "obsidian";
10 | import {
11 | DEFAULT_SETTINGS,
12 | TableExtendedSettings,
13 | TableExtendedSettingTab,
14 | } from "settings";
15 |
16 | import Export2PDFHack from "./hack-pdf";
17 | import { mditOptions, renderMarkdown } from "./render";
18 |
19 | const prefixPatternInMD = /^(?:>\s*)?-tx-\n/;
20 |
21 | export default class TableExtended extends Plugin {
22 | settings: TableExtendedSettings = DEFAULT_SETTINGS;
23 |
24 | print2pdfFileCache: TFile | null = null;
25 |
26 | async loadSettings() {
27 | this.settings = { ...this.settings, ...(await this.loadData()) };
28 | }
29 |
30 | async saveSettings() {
31 | await this.saveData(this.settings);
32 | }
33 |
34 | constructor(...args: ConstructorParameters) {
35 | super(...args);
36 | this.mdit = MarkdownIt(mditOptions).use(mTable, {
37 | multiline: true,
38 | rowspan: true,
39 | headerless: true,
40 | });
41 | /** keep only table required features, let obsidian handle the markdown inside cell */
42 | this.mdit.block.ruler.enableOnly([
43 | "code",
44 | "fence",
45 | "table",
46 | "paragraph",
47 | "reference",
48 | "blockquote",
49 | ]);
50 | this.mdit.inline.ruler.enableOnly([]);
51 | }
52 | mdit: MarkdownIt;
53 |
54 | processNativeTable = (el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
55 | if (!el.querySelector("table")) return;
56 |
57 | const raw = getSourceMarkdown(el, ctx);
58 | if (!raw) {
59 | console.warn("failed to get Markdown text, escaping...");
60 | return;
61 | }
62 | el.empty();
63 | this.renderFromMD(raw, el, ctx);
64 | };
65 | processTextSection = (el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
66 | // el contains only els for one block in preview;
67 | // el contains els for all blocks in export2pdf
68 | for (const child of el.children) {
69 | let p: HTMLParagraphElement;
70 | if (child instanceof HTMLParagraphElement) {
71 | p = child;
72 | } else if (
73 | child instanceof HTMLQuoteElement &&
74 | child.firstElementChild instanceof HTMLParagraphElement
75 | ) {
76 | p = child.firstElementChild;
77 | } else continue;
78 |
79 | let result;
80 | if (p.innerHTML.startsWith("-tx-")) {
81 | const src = getSourceMarkdown(el, ctx);
82 | if (!src) {
83 | console.warn("failed to get Markdown text, escaping...");
84 | } else if ((result = src.match(prefixPatternInMD))) {
85 | const footnoteSelector = "sup.footnote-ref";
86 | // save footnote refs
87 | const footnoteRefs = [
88 | ...el.querySelectorAll(footnoteSelector),
89 | ] as HTMLElement[];
90 | // footnote refs is replaced by new ones during rendering
91 | this.renderFromMD(src.substring(result[0].length), el, ctx);
92 | // post process to revert footnote refs
93 | for (const newRefs of el.querySelectorAll(footnoteSelector)) {
94 | newRefs.replaceWith(footnoteRefs.shift()!);
95 | }
96 | for (const fnSection of el.querySelectorAll("section.footnotes")) {
97 | fnSection.remove();
98 | }
99 | }
100 | }
101 | }
102 | };
103 |
104 | async onload(): Promise {
105 | console.log("loading table-extended");
106 | await this.loadSettings();
107 | this.addSettingTab(new TableExtendedSettingTab(this.app, this));
108 | if (this.settings.hackPDF) {
109 | Export2PDFHack(this);
110 | }
111 |
112 | if (this.settings.handleNativeTable)
113 | MarkdownPreviewRenderer.registerPostProcessor(this.processNativeTable);
114 |
115 | this.registerMarkdownCodeBlockProcessor("tx", this.renderFromMD);
116 | this.registerMarkdownPostProcessor(this.processTextSection);
117 |
118 | // Read Obsidian's config to keep "strictLineBreaks" option in sync
119 | this.mdit.set({
120 | breaks: !this.app.vault.getConfig("strictLineBreaks"),
121 | });
122 | this.app.workspace.onLayoutReady(this.refresh);
123 | }
124 |
125 | onunload() {
126 | console.log("unloading table-extended");
127 | MarkdownPreviewRenderer.unregisterPostProcessor(this.processNativeTable);
128 | this.refresh();
129 | this.print2pdfFileCache = null;
130 | }
131 | /** refresh opened MarkdownView */
132 | refresh = () =>
133 | this.app.workspace.iterateAllLeaves((leaf) =>
134 | setTimeout(() => {
135 | if (leaf.view instanceof MarkdownView) {
136 | leaf.view.previewMode.rerender(true);
137 | }
138 | }, 200),
139 | );
140 |
141 | renderFromMD = renderMarkdown.bind(this);
142 | }
143 |
144 | const getSourceMarkdown = (
145 | sectionEl: HTMLElement,
146 | ctx: MarkdownPostProcessorContext,
147 | ): string | null => {
148 | let info = ctx.getSectionInfo(sectionEl);
149 | if (info) {
150 | const { text, lineStart, lineEnd } = info;
151 | return text
152 | .split("\n")
153 | .slice(lineStart, lineEnd + 1)
154 | .join("\n");
155 | } else {
156 | return null;
157 | }
158 | };
159 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "outDir": "",
5 | "inlineSourceMap": true,
6 | "inlineSources": true,
7 | "module": "ESNext",
8 | "target": "es6",
9 | "strict": true,
10 | "allowJs": true,
11 | "noImplicitAny": true,
12 | "moduleResolution": "node",
13 | "importHelpers": true,
14 | "allowSyntheticDefaultImports": true,
15 | "lib": [
16 | "dom",
17 | "es5",
18 | "scripthost",
19 | "es2015",
20 | "dom.iterable",
21 | "ES2020.String"
22 | ]
23 | },
24 | "include": ["src/*.ts"]
25 | }
26 |
--------------------------------------------------------------------------------
/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "1.0.0": "0.10.12",
3 | "1.0.1": "0.10.12",
4 | "1.1.1": "0.10.12",
5 | "1.2.0": "0.12.0",
6 | "1.3.0": "0.12.0",
7 | "1.3.1": "0.12.0",
8 | "1.3.2": "0.12.0",
9 | "1.4.0": "0.12.0",
10 | "1.4.1": "0.12.0",
11 | "1.5.0": "0.12.0",
12 | "1.5.1": "0.12.0",
13 | "1.6.0": "0.12.0",
14 | "1.6.1": "0.12.0"
15 | }
16 |
--------------------------------------------------------------------------------