├── .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 | ![image](https://user-images.githubusercontent.com/31102694/123046427-ad89b780-d42e-11eb-9588-b7028954bcfc.png) 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 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |
Prototype table
Grouping
First HeaderSecond HeaderThird Header
ContentLong Cell
ContentCellCell
New sectionMoreData
And moreWith an escaped '|'
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 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
First HeaderSecond HeaderThird Header
ContentLong Cell
ContentCellCell
New sectionMoreData
And moreWith an escaped '|'
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 | 188 | 189 | 190 | 191 | 192 | 193 | 197 | 200 | 201 | 202 | 206 | 212 | 213 | 214 | 219 | 223 | 224 | 225 |
MarkdownRendered HTML
194 |
*Italic*
195 | 
196 |
198 |

Italic

199 |
203 |
- Item 1
204 | - Item 2
205 |
207 |
    208 |
  • Item 1
  • 209 |
  • Item 2
  • 210 |
211 |
215 |
```python
216 | .1 + .2
217 | ```
218 |
220 |
.1 + .2
221 | 
222 |
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 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 |
StageDirect ProductsATP Yields
Glycolysis2 ATP
2 NADH3–5 ATP
Pyruvaye oxidation2 NADH5 ATP
Citric acid cycle2 ATP
6 NADH15 ATP
2 FADH23 ATP
30–32 ATP
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 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 |
Grouping
First HeaderSecond HeaderThird Header
ContentLong Cell
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 | --------------------------------------------------------------------------------