Compare commits

...

51 Commits
fork ... 9.0.2

Author SHA1 Message Date
5be745b081 build(deps-dev): bump cspell from 8.15.1 to 8.15.2 (#2766)
Bumps [cspell](https://github.com/streetsidesoftware/cspell/tree/HEAD/packages/cspell) from 8.15.1 to 8.15.2.
- [Release notes](https://github.com/streetsidesoftware/cspell/releases)
- [Changelog](https://github.com/streetsidesoftware/cspell/blob/main/packages/cspell/CHANGELOG.md)
- [Commits](https://github.com/streetsidesoftware/cspell/commits/v8.15.2/packages/cspell)

---
updated-dependencies:
- dependency-name: cspell
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-15 00:38:30 +01:00
b4fcfd6386 Version bump 2024-10-14 01:51:16 +01:00
6e371d42a7 fix: npm publish install 2024-10-14 01:47:59 +01:00
e8564d58c6 Create SECURITY.md (#2765) 2024-10-14 00:09:03 +01:00
368aa431a0 build(deps-dev): bump vite-tsconfig-paths from 4.2.3 to 5.0.1 (#2758)
Bumps [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) from 4.2.3 to 5.0.1.
- [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases)
- [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v4.2.3...v5.0.1)

---
updated-dependencies:
- dependency-name: vite-tsconfig-paths
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-13 23:02:22 +01:00
afcd1ae060 build(deps-dev): bump @vitest/ui from 1.4.0 to 2.1.2 (#2757)
Bumps [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) from 1.4.0 to 2.1.2.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.2/packages/ui)

---
updated-dependencies:
- dependency-name: "@vitest/ui"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-13 23:02:12 +01:00
244d2b8904 build(deps-dev): bump cspell from 8.3.2 to 8.15.1 (#2763)
Bumps [cspell](https://github.com/streetsidesoftware/cspell/tree/HEAD/packages/cspell) from 8.3.2 to 8.15.1.
- [Release notes](https://github.com/streetsidesoftware/cspell/releases)
- [Changelog](https://github.com/streetsidesoftware/cspell/blob/main/packages/cspell/CHANGELOG.md)
- [Commits](https://github.com/streetsidesoftware/cspell/commits/v8.15.1/packages/cspell)

---
updated-dependencies:
- dependency-name: cspell
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 20:27:03 +01:00
6dae62e1ab build(deps-dev): bump vite-plugin-dts from 3.7.0 to 4.2.4 (#2761)
Bumps [vite-plugin-dts](https://github.com/qmhc/vite-plugin-dts) from 3.7.0 to 4.2.4.
- [Release notes](https://github.com/qmhc/vite-plugin-dts/releases)
- [Changelog](https://github.com/qmhc/vite-plugin-dts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/qmhc/vite-plugin-dts/compare/v3.7.0...v4.2.4)

---
updated-dependencies:
- dependency-name: vite-plugin-dts
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 20:26:54 +01:00
5810cb5f02 build(deps-dev): bump ws from 7.5.7 to 7.5.10 (#2764)
Bumps [ws](https://github.com/websockets/ws) from 7.5.7 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.7...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 20:26:41 +01:00
11c26af3a9 build(deps-dev): bump eslint-plugin-unicorn from 50.0.1 to 56.0.0 (#2756)
Bumps [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) from 50.0.1 to 56.0.0.
- [Release notes](https://github.com/sindresorhus/eslint-plugin-unicorn/releases)
- [Commits](https://github.com/sindresorhus/eslint-plugin-unicorn/compare/v50.0.1...v56.0.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-unicorn
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 20:26:19 +01:00
1b9b815214 build(deps-dev): bump eslint-plugin-functional from 6.0.0 to 6.6.3 (#2755)
Bumps [eslint-plugin-functional](https://github.com/eslint-functional/eslint-plugin-functional) from 6.0.0 to 6.6.3.
- [Release notes](https://github.com/eslint-functional/eslint-plugin-functional/releases)
- [Changelog](https://github.com/eslint-functional/eslint-plugin-functional/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-functional/eslint-plugin-functional/compare/v6.0.0...v6.6.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-functional
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-13 20:26:05 +01:00
c342137c6f build(deps-dev): bump jsdom from 24.0.0 to 25.0.1 (#2754)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 24.0.0 to 25.0.1.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Changelog](https://github.com/jsdom/jsdom/blob/main/Changelog.md)
- [Commits](https://github.com/jsdom/jsdom/compare/24.0.0...25.0.1)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 02:55:07 +01:00
eefc8bd8a5 build(deps-dev): bump unzipper from 0.10.14 to 0.12.3 (#2753)
Bumps [unzipper](https://github.com/ZJONSSON/node-unzipper) from 0.10.14 to 0.12.3.
- [Release notes](https://github.com/ZJONSSON/node-unzipper/releases)
- [Commits](https://github.com/ZJONSSON/node-unzipper/commits/v0.12.3)

---
updated-dependencies:
- dependency-name: unzipper
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-13 02:54:58 +01:00
9873861210 build(deps-dev): bump glob from 10.3.10 to 11.0.0 (#2752)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.3.10 to 11.0.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.3.10...v11.0.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-13 02:34:04 +01:00
cf7a05e05d build(deps-dev): bump tsx from 4.7.0 to 4.19.1 (#2750)
Bumps [tsx](https://github.com/privatenumber/tsx) from 4.7.0 to 4.19.1.
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.7.0...v4.19.1)

---
updated-dependencies:
- dependency-name: tsx
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 02:14:45 +01:00
6eb11d8842 Revert github action version change 2024-10-13 02:06:22 +01:00
3ad68337e7 Version bump 2024-10-13 01:58:44 +01:00
07f363fcb7 feat: Add npm publish GitHub Action workflow (#2762)
* feat: Add npm publish GitHub Action workflow

* Fix typo
2024-10-13 01:48:04 +01:00
2d2e4cdab2 fix: Missing text property for patchDocument (#2760)
* fix: Missing text property

Ignore <br /> for patched files

* Fix spelling issues
2024-10-13 01:29:32 +01:00
0cadec7f58 build(deps-dev): bump @typescript-eslint/parser from 6.17.0 to 8.8.1 (#2742)
* build(deps-dev): bump @typescript-eslint/parser from 6.17.0 to 8.8.1

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 6.17.0 to 8.8.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.8.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix type definitions

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan Miu <dolan_miu@hotmail.com>
2024-10-11 04:47:51 +01:00
e86dbd3398 Change ImageRun keys to be based on image data content (#2681)
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 01:13:51 +01:00
021f1b0c4d Update bullet-points.md (#2684)
Excluding ; from example

Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 01:13:26 +01:00
5aa2027252 build(deps): bump send and serve-static (#2736)
Bumps [send](https://github.com/pillarjs/send) and [serve-static](https://github.com/expressjs/serve-static). These dependencies needed to be updated together.

Updates `send` from 0.17.2 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.17.2...0.19.0)

Updates `serve-static` from 1.14.2 to 1.16.2
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.14.2...v1.16.2)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: serve-static
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 01:13:14 +01:00
2fc297ef3c build(deps-dev): bump braces from 3.0.2 to 3.0.3 (#2749)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 01:12:50 +01:00
3fe216846c build(deps-dev): bump dompurify from 2.3.6 to 2.5.7 (#2737)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.6 to 2.5.7.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.6...2.5.7)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 01:05:41 +01:00
649a50f7c5 build(deps-dev): bump serve-static from 1.14.2 to 1.16.2 (#2739)
Bumps [serve-static](https://github.com/expressjs/serve-static) from 1.14.2 to 1.16.2.
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.14.2...v1.16.2)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 01:05:35 +01:00
dc14123a5a build(deps-dev): bump rollup from 3.29.4 to 4.24.0 (#2740)
Bumps [rollup](https://github.com/rollup/rollup) from 3.29.4 to 4.24.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.29.4...v4.24.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 01:05:25 +01:00
048a035a5d build(deps-dev): bump elliptic from 6.5.4 to 6.5.7 (#2738)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.4 to 6.5.7.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.4...v6.5.7)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 00:58:47 +01:00
f212cf0251 Update styling-with-js.md (#2734)
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 00:57:11 +01:00
1f2f5988e1 build(deps-dev): bump vite from 5.0.10 to 5.4.8 (#2741)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.10 to 5.4.8.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.8/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.8/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 00:56:49 +01:00
f1359a4750 build(deps-dev): bump @types/prompt from 1.1.8 to 1.1.9 (#2746)
Bumps [@types/prompt](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/prompt) from 1.1.8 to 1.1.9.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/prompt)

---
updated-dependencies:
- dependency-name: "@types/prompt"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 00:33:21 +01:00
f2775ed13c build(deps): bump @types/node from 20.12.4 to 22.7.5 (#2747)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.12.4 to 22.7.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 00:33:12 +01:00
3ae749278e build(deps-dev): bump eslint-plugin-jsdoc from 48.0.2 to 50.3.1 (#2745)
Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 48.0.2 to 50.3.1.
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Changelog](https://github.com/gajus/eslint-plugin-jsdoc/blob/main/.releaserc)
- [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v48.0.2...v50.3.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-jsdoc
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-10-11 00:30:35 +01:00
0bcc6910f4 Use codecov token (#2748)
* Use codecov token

* Using env to pass token
2024-10-11 00:22:43 +01:00
c15951550c Version bump 2024-10-10 01:30:32 +01:00
8308b6413e Add documentation 2024-10-10 00:38:25 +01:00
5b75875684 Enable rollupTypes in the vite build configuration. (#2714)
This resolves issues with in dependent TypeScipt packages that are using moduleResolution: NodeNext
Modern ES modules cannot use extensionless relative paths in imports.
2024-10-10 00:15:04 +01:00
28029f4c1c chore[tables.md]: replace semicolon to commas (#2708) 2024-10-10 00:14:09 +01:00
824d7f9893 build(deps-dev): bump vite from 5.0.10 to 5.2.8 (#2668)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.10 to 5.2.8.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.2.8/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 00:13:52 +01:00
b3aea4b9a0 build(deps-dev): bump @vitest/coverage-v8 from 1.1.1 to 1.4.0 (#2645)
Bumps [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) from 1.1.1 to 1.4.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.4.0/packages/coverage-v8)

---
updated-dependencies:
- dependency-name: "@vitest/coverage-v8"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 00:10:13 +01:00
32377d187d build(deps-dev): bump @vitest/ui from 1.1.1 to 1.4.0 (#2644)
Bumps [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) from 1.1.1 to 1.4.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.4.0/packages/ui)

---
updated-dependencies:
- dependency-name: "@vitest/ui"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 21:26:07 +01:00
a80815822d build(deps-dev): bump vitest from 1.1.1 to 1.4.0 (#2642)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.1.1 to 1.4.0.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v1.4.0/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 21:26:01 +01:00
c198154fdc build(deps-dev): bump typedoc from 0.25.6 to 0.25.12 (#2634)
Bumps [typedoc](https://github.com/TypeStrong/TypeDoc) from 0.25.6 to 0.25.12.
- [Release notes](https://github.com/TypeStrong/TypeDoc/releases)
- [Changelog](https://github.com/TypeStrong/typedoc/blob/master/CHANGELOG.md)
- [Commits](https://github.com/TypeStrong/TypeDoc/compare/v0.25.6...v0.25.12)

---
updated-dependencies:
- dependency-name: typedoc
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 21:25:52 +01:00
618c7a8578 build(deps-dev): bump @typescript-eslint/eslint-plugin (#2595)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 6.17.0 to 7.0.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.0.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 21:25:44 +01:00
ef7b930d4d build(deps-dev): bump prettier from 3.1.1 to 3.2.5 (#2579)
Bumps [prettier](https://github.com/prettier/prettier) from 3.1.1 to 3.2.5.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.1.1...3.2.5)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 21:25:29 +01:00
8296895cc6 build(deps-dev): bump jsdom from 23.0.1 to 24.0.0 (#2557)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 23.0.1 to 24.0.0.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Changelog](https://github.com/jsdom/jsdom/blob/main/Changelog.md)
- [Commits](https://github.com/jsdom/jsdom/compare/23.0.1...24.0.0)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 21:25:12 +01:00
1dce6fee15 build(deps): bump @types/node from 20.10.6 to 20.12.4 (#2669)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.10.6 to 20.12.4.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 21:25:03 +01:00
444e7771b4 Version bump 2024-10-09 21:21:08 +01:00
962795743c feat: Add ability to detect patches which are present in a file (#2633)
* feat: Add ability to detect patches which are present in a file

* chore: export patchDetector function

* fix: Make sure we don't attempt to call toJson on binary content

---------

Co-authored-by: Christopher Fox <cfox@homebound.com>
2024-05-22 03:05:48 +01:00
f98f852a55 Allow disabling numbering inherited from a paragraph style (#2531) 2024-05-20 03:15:11 +01:00
e379a7fe04 Document the 'keepOriginalStyles' options (#2549)
Co-authored-by: Dolan <dolan_miu@hotmail.com>
2024-05-20 03:13:05 +01:00
33 changed files with 5817 additions and 3674 deletions

View File

@ -13,10 +13,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@master uses: actions/checkout@v4
- uses: "./.github/actions/install-and-build" - uses: "./.github/actions/install-and-build"
- name: Archive Production Artifact - name: Archive Production Artifact
uses: actions/upload-artifact@master uses: actions/upload-artifact@v4
with: with:
name: build name: build
path: build path: build
@ -25,22 +25,24 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@master uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: npm ci --force run: npm ci --force
- name: Test - name: Test
run: npm run test:ci run: npm run test:ci
- name: Codecov - name: Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v4
with: with:
fail_ci_if_error: true fail_ci_if_error: true
verbose: true verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
lint: lint:
name: Lint name: Lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@master uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: npm ci --force run: npm ci --force
- name: Lint - name: Lint
@ -50,7 +52,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@master uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: npm ci --force run: npm ci --force
- name: Prettier - name: Prettier
@ -60,7 +62,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@master uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: npm ci --force run: npm ci --force
- name: Prettier - name: Prettier

View File

@ -12,7 +12,7 @@ jobs:
name: Demos name: Demos
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v4
- uses: "./.github/actions/install-and-build" - uses: "./.github/actions/install-and-build"
- name: Run Demos - name: Run Demos
run: npm run run-ts -- ./demo/1-basic.ts run: npm run run-ts -- ./demo/1-basic.ts

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@master uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: npm ci --force run: npm ci --force
- name: Build 🔧 - name: Build 🔧
@ -19,7 +19,7 @@ jobs:
echo "docx.js.org" > docs/.nojekyll echo "docx.js.org" > docs/.nojekyll
echo "docx.js.org" > docs/CNAME echo "docx.js.org" > docs/CNAME
- name: Archive Production Artifact - name: Archive Production Artifact
uses: actions/upload-artifact@master uses: actions/upload-artifact@v4
with: with:
name: docs name: docs
path: docs path: docs
@ -28,11 +28,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repo 🛎️ - name: Checkout Repo 🛎️
uses: actions/checkout@master uses: actions/checkout@v4
- name: Install Dependencies - name: Install Dependencies
run: npm ci --force run: npm ci --force
- name: Download Artifact - name: Download Artifact
uses: actions/download-artifact@master uses: actions/download-artifact@v4
with: with:
name: docs name: docs
path: docs path: docs

46
.github/workflows/npm-publish.yml vendored Normal file
View File

@ -0,0 +1,46 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
name: Node.js Package
on:
release:
types: [created]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20.x"
- run: npm ci --force
- run: npm run cspell
- run: npm run prettier
- run: npm run lint
- run: npm run test:ci
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20.x"
- run: npm ci --force
- run: npm run build
publish-npm:
needs: [test, build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20.x"
registry-url: https://registry.npmjs.org/
- run: npm ci --force
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

24
SECURITY.md Normal file
View File

@ -0,0 +1,24 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 9.0.x | :white_check_mark: |
## Reporting a Vulnerability
We encourage responsible disclosure of security vulnerabilities. If you believe you have found a security vulnerability in this project, please report it via the [Security Tab](https://github.com/dolanmiu/docx/security/advisories)
Please include the following information in your report:
* A description of the vulnerability
* Steps to reproduce the vulnerability
* Impact of the vulnerability
We will investigate all reported vulnerabilities and take appropriate action.
We appreciate your help in keeping this project secure.

View File

@ -0,0 +1,72 @@
// Patch a document with patches
import * as fs from "fs";
import { patchDocument, PatchType, TextRun } from "docx";
patchDocument({
outputType: "nodebuffer",
data: fs.readFileSync("demo/assets/field-trip.docx"),
patches: {
todays_date: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: new Date().toLocaleDateString() })],
},
school_name: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
address: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "blah blah" })],
},
city: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
state: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
zip: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
phone: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
first_name: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
last_name: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
email_address: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
ft_dates: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
grade: {
type: PatchType.PARAGRAPH,
children: [new TextRun({ text: "test" })],
},
},
}).then((doc) => {
fs.writeFileSync("My Document.docx", doc);
});

BIN
demo/assets/field-trip.docx Normal file

Binary file not shown.

View File

@ -22,7 +22,7 @@ const doc = new Document({
} }
}) })
], ],
}]; }]
}); });
``` ```

View File

@ -130,6 +130,62 @@ new Paragraph({
}), }),
``` ```
## Disabling numbering inherited from paragraph style
If the numbering is set on a paragraph style, you may wish to disable it for a specific paragraph:
```ts
const doc = new Document({
...
numbering: {
config: [
{
reference: "my-bullet-points",
levels: [
{
level: 0,
format: LevelFormat.BULLET,
text: "\u1F60",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: convertInchesToTwip(0.5), hanging: convertInchesToTwip(0.25) },
},
},
},
],
},
],
},
styles: {
paragraphStyles: [
{
id: 'bullet',
name: 'Bullet',
basedOn: 'Normal',
next: 'Normal',
run: {},
paragraph: {
numbering: {
reference: 'my-bullet-points',
level: 0,
},
},
},
],
},
...
});
```
```ts
new Paragraph({
text: "No bullet points!",
style: "Bullet",
numbering: false,
}),
```
## Full Example ## Full Example
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/3-numbering-and-bullet-points.ts ":include") [Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/3-numbering-and-bullet-points.ts ":include")

View File

@ -35,6 +35,9 @@ interface Patch {
| type | `PatchType` | Required | `DOCUMENT`, `PARAGRAPH` | | type | `PatchType` | Required | `DOCUMENT`, `PARAGRAPH` |
| children | `FileChild[] or ParagraphChild[]` | Required | The contents to replace with. A `FileChild` is a `Paragraph` or `Table`, whereas a `ParagraphChild` is typical `Paragraph` children. | | children | `FileChild[] or ParagraphChild[]` | Required | The contents to replace with. A `FileChild` is a `Paragraph` or `Table`, whereas a `ParagraphChild` is typical `Paragraph` children. |
The patcher also takes in a `keepOriginalStyles` boolean, which will preserve the styles of the patched text when set to true.
### How to patch existing document ### How to patch existing document
1. Open your existing word document in your favorite Word Processor 1. Open your existing word document in your favorite Word Processor

View File

@ -126,10 +126,10 @@ const doc = new Document({
next: "Normal", next: "Normal",
quickFormat: true, quickFormat: true,
run: { run: {
size: 26 size: 26,
bold: true, bold: true,
color: "999999", color: "999999",
{ underline: {
type: UnderlineType.DOUBLE, type: UnderlineType.DOUBLE,
color: "FF0000", color: "FF0000",
}, },

View File

@ -22,7 +22,7 @@ Then add the table in the `section`
const doc = new Document({ const doc = new Document({
sections: [{ sections: [{
children: [table], children: [table],
}]; }],
}); });
``` ```

8094
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "docx", "name": "docx",
"version": "8.5.0", "version": "9.0.2",
"description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.", "description": "Easily generate .docx files with JS/TS with a nice declarative API. Works for Node and on the Browser.",
"type": "module", "type": "module",
"main": "build/index.umd.js", "main": "build/index.umd.js",
@ -54,7 +54,8 @@
"clippy" "clippy"
], ],
"dependencies": { "dependencies": {
"@types/node": "^20.3.1", "@types/node": "^22.7.5",
"hash.js": "^1.1.7",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"xml": "^1.0.1", "xml": "^1.0.1",
@ -71,34 +72,34 @@
"@types/prompt": "^1.1.1", "@types/prompt": "^1.1.1",
"@types/unzipper": "^0.10.4", "@types/unzipper": "^0.10.4",
"@types/xml": "^1.0.8", "@types/xml": "^1.0.8",
"@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^6.9.1", "@typescript-eslint/parser": "^8.8.1",
"@vitest/coverage-v8": "^1.1.0", "@vitest/coverage-v8": "^1.1.0",
"@vitest/ui": "^1.1.0", "@vitest/ui": "^2.1.2",
"cspell": "^8.2.3", "cspell": "^8.2.3",
"docsify-cli": "^4.3.0", "docsify-cli": "^4.3.0",
"eslint": "^8.23.0", "eslint": "^8.23.0",
"eslint-plugin-functional": "^6.0.0", "eslint-plugin-functional": "^6.0.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^48.0.2", "eslint-plugin-jsdoc": "^50.3.1",
"eslint-plugin-no-null": "^1.0.2", "eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-unicorn": "^50.0.1", "eslint-plugin-unicorn": "^56.0.0",
"execa": "^8.0.1", "execa": "^8.0.1",
"glob": "^10.2.7", "glob": "^11.0.0",
"inquirer": "^9.2.7", "inquirer": "^9.2.7",
"jsdom": "^23.0.1", "jsdom": "^25.0.1",
"pre-commit": "^1.2.2", "pre-commit": "^1.2.2",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"tsconfig-paths": "^4.0.0", "tsconfig-paths": "^4.0.0",
"tsx": "^4.7.0", "tsx": "^4.7.0",
"typedoc": "^0.25.4", "typedoc": "^0.25.4",
"typescript": "5.3.3", "typescript": "5.3.3",
"unzipper": "^0.10.11", "unzipper": "^0.12.3",
"vite": "^5.0.10", "vite": "^5.0.10",
"vite-plugin-dts": "^3.3.1", "vite-plugin-dts": "^4.2.4",
"vite-plugin-node-polyfills": "^0.19.0", "vite-plugin-node-polyfills": "^0.19.0",
"vite-tsconfig-paths": "^4.2.0", "vite-tsconfig-paths": "^5.0.1",
"vitest": "^1.1.0" "vitest": "^1.1.0"
}, },
"engines": { "engines": {

View File

@ -7,6 +7,12 @@ export type EffectExtentAttributes = {
readonly left: number; readonly left: number;
}; };
// <xsd:complexType name="CT_EffectExtent">
// <xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
// <xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
// <xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
// <xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
// </xsd:complexType>
export const createEffectExtent = ({ top, right, bottom, left }: EffectExtentAttributes): XmlComponent => export const createEffectExtent = ({ top, right, bottom, left }: EffectExtentAttributes): XmlComponent =>
new BuilderElement<EffectExtentAttributes>({ new BuilderElement<EffectExtentAttributes>({
name: "wp:effectExtent", name: "wp:effectExtent",

View File

@ -24,6 +24,8 @@ class SpacingAttributes extends XmlAttributeComponent<ISpacingProperties> {
before: "w:before", before: "w:before",
line: "w:line", line: "w:line",
lineRule: "w:lineRule", lineRule: "w:lineRule",
beforeAutoSpacing: "w:beforeAutospacing",
afterAutoSpacing: "w:afterAutoSpacing",
}; };
} }

View File

@ -65,6 +65,36 @@ describe("ParagraphProperties", () => {
}); });
}); });
it("should create with numbering disabled", () => {
const properties = new ParagraphProperties({
numbering: false,
});
const tree = new Formatter().format(properties);
expect(tree).to.deep.equal({
"w:pPr": [
{
"w:numPr": [
{
"w:ilvl": {
_attr: {
"w:val": 0,
},
},
},
{
"w:numId": {
_attr: {
"w:val": 0,
},
},
},
],
},
],
});
});
it("should create with widowControl", () => { it("should create with widowControl", () => {
const properties = new ParagraphProperties({ const properties = new ParagraphProperties({
widowControl: true, widowControl: true,

View File

@ -37,12 +37,14 @@ export interface ILevelParagraphStylePropertiesOptions {
} }
export interface IParagraphStylePropertiesOptions extends ILevelParagraphStylePropertiesOptions { export interface IParagraphStylePropertiesOptions extends ILevelParagraphStylePropertiesOptions {
readonly numbering?: { readonly numbering?:
readonly reference: string; | {
readonly level: number; readonly reference: string;
readonly instance?: number; readonly level: number;
readonly custom?: boolean; readonly instance?: number;
}; readonly custom?: boolean;
}
| false;
} }
export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOptions { export interface IParagraphPropertiesOptions extends IParagraphStylePropertiesOptions {
@ -135,6 +137,8 @@ export class ParagraphProperties extends IgnoreIfEmptyXmlComponent {
}); });
this.push(new NumberProperties(`${options.numbering.reference}-${options.numbering.instance ?? 0}`, options.numbering.level)); this.push(new NumberProperties(`${options.numbering.reference}-${options.numbering.instance ?? 0}`, options.numbering.level));
} else if (options.numbering === false) {
this.push(new NumberProperties(0, 0));
} }
if (options.border) { if (options.border) {

View File

@ -1,21 +1,12 @@
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { Formatter } from "@export/formatter"; import { Formatter } from "@export/formatter";
import { IViewWrapper } from "@file/document-wrapper"; import { IViewWrapper } from "@file/document-wrapper";
import { File } from "@file/file"; import { File } from "@file/file";
import * as convenienceFunctions from "@util/convenience-functions";
import { ImageRun } from "./image-run"; import { ImageRun } from "./image-run";
describe("ImageRun", () => { describe("ImageRun", () => {
beforeAll(() => {
vi.spyOn(convenienceFunctions, "uniqueId").mockReturnValue("test-unique-id");
});
afterAll(() => {
vi.resetAllMocks();
});
describe("#constructor()", () => { describe("#constructor()", () => {
it("should create with Buffer", () => { it("should create with Buffer", () => {
const currentImageRun = new ImageRun({ const currentImageRun = new ImageRun({
@ -193,7 +184,8 @@ describe("ImageRun", () => {
"a:blip": { "a:blip": {
_attr: { _attr: {
cstate: "none", cstate: "none",
"r:embed": "rId{test-unique-id.png}", "r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
}, },
}, },
}, },
@ -445,7 +437,8 @@ describe("ImageRun", () => {
"a:blip": { "a:blip": {
_attr: { _attr: {
cstate: "none", cstate: "none",
"r:embed": "rId{test-unique-id.png}", "r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
}, },
}, },
}, },
@ -700,7 +693,8 @@ describe("ImageRun", () => {
"a:blip": { "a:blip": {
_attr: { _attr: {
cstate: "none", cstate: "none",
"r:embed": "rId{test-unique-id.png}", "r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
}, },
}, },
}, },
@ -955,7 +949,8 @@ describe("ImageRun", () => {
"a:blip": { "a:blip": {
_attr: { _attr: {
cstate: "none", cstate: "none",
"r:embed": "rId{test-unique-id.png}", "r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
}, },
}, },
}, },
@ -1090,7 +1085,8 @@ describe("ImageRun", () => {
{ {
_attr: { _attr: {
cstate: "none", cstate: "none",
"r:embed": "rId{test-unique-id.png}", "r:embed":
"rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.png}",
}, },
}, },
{ {
@ -1106,7 +1102,7 @@ describe("ImageRun", () => {
"asvg:svgBlip": { "asvg:svgBlip": {
_attr: expect.objectContaining({ _attr: expect.objectContaining({
"r:embed": "r:embed":
"rId{test-unique-id.svg}", "rId{da39a3ee5e6b4b0d3255bfef95601890afd80709.svg}",
}), }),
}, },
}, },
@ -1131,5 +1127,55 @@ describe("ImageRun", () => {
], ],
}); });
}); });
it("using same data twice should use same media key", () => {
const imageRunStringData = new ImageRun({
type: "png",
data: "DATA",
transformation: {
width: 100,
height: 100,
rotation: 42,
},
});
const imageRunBufferData = new ImageRun({
type: "png",
data: Buffer.from("DATA"),
transformation: {
width: 200,
height: 200,
rotation: 45,
},
});
const addImageSpy = vi.fn();
const context = {
file: {
Media: {
addImage: addImageSpy,
},
} as unknown as File,
viewWrapper: {} as unknown as IViewWrapper,
stack: [],
};
new Formatter().format(imageRunStringData, context);
new Formatter().format(imageRunBufferData, context);
const expectedHash = "580393f5a94fb469585f5dd2a6859a4aab899f37";
expect(addImageSpy).toHaveBeenCalledTimes(2);
expect(addImageSpy).toHaveBeenNthCalledWith(
1,
`${expectedHash}.png`,
expect.objectContaining({ fileName: `${expectedHash}.png` }),
);
expect(addImageSpy).toHaveBeenNthCalledWith(
2,
`${expectedHash}.png`,
expect.objectContaining({ fileName: `${expectedHash}.png` }),
);
});
}); });
}); });

View File

@ -1,4 +1,4 @@
import { uniqueId } from "@util/convenience-functions"; import { hashedId } from "@util/convenience-functions";
import { IContext, IXmlableObject } from "@file/xml-components"; import { IContext, IXmlableObject } from "@file/xml-components";
import { DocPropertiesOptions } from "@file/drawing/doc-properties/doc-properties"; import { DocPropertiesOptions } from "@file/drawing/doc-properties/doc-properties";
@ -76,20 +76,19 @@ const createImageData = (options: IImageOptions, key: string): Pick<IMediaData,
}); });
export class ImageRun extends Run { export class ImageRun extends Run {
private readonly key: string;
private readonly fallbackKey = `${uniqueId()}.png`;
private readonly imageData: IMediaData; private readonly imageData: IMediaData;
public constructor(options: IImageOptions) { public constructor(options: IImageOptions) {
super({}); super({});
this.key = `${uniqueId()}.${options.type}`; const hash = hashedId(options.data);
const key = `${hash}.${options.type}`;
this.imageData = this.imageData =
options.type === "svg" options.type === "svg"
? { ? {
type: options.type, type: options.type,
...createImageData(options, this.key), ...createImageData(options, key),
fallback: { fallback: {
type: options.fallback.type, type: options.fallback.type,
...createImageData( ...createImageData(
@ -97,13 +96,13 @@ export class ImageRun extends Run {
...options.fallback, ...options.fallback,
transformation: options.transformation, transformation: options.transformation,
}, },
this.fallbackKey, `${hashedId(options.fallback.data)}.${options.fallback.type}`,
), ),
}, },
} }
: { : {
type: options.type, type: options.type,
...createImageData(options, this.key), ...createImageData(options, key),
}; };
const drawing = new Drawing(this.imageData, { const drawing = new Drawing(this.imageData, {
floating: options.floating, floating: options.floating,
@ -115,10 +114,10 @@ export class ImageRun extends Run {
} }
public prepForXml(context: IContext): IXmlableObject | undefined { public prepForXml(context: IContext): IXmlableObject | undefined {
context.file.Media.addImage(this.key, this.imageData); context.file.Media.addImage(this.imageData.fileName, this.imageData);
if (this.imageData.type === "svg") { if (this.imageData.type === "svg") {
context.file.Media.addImage(this.fallbackKey, this.imageData.fallback); context.file.Media.addImage(this.imageData.fallback.fileName, this.imageData.fallback);
} }
return super.prepForXml(context); return super.prepForXml(context);

View File

@ -1,12 +1,13 @@
import { BaseXmlComponent, IContext } from "./base"; import { BaseXmlComponent, IContext } from "./base";
import { IXmlableObject, IXmlAttribute } from "./xmlable-object"; import { IXmlableObject, IXmlAttribute } from "./xmlable-object";
export type AttributeMap<T> = { readonly [P in keyof T]: string }; type AttributeMap<T> = Record<keyof T, string>;
export type AttributeData = { readonly [key: string]: boolean | number | string }; export type AttributeData = { readonly [key: string]: boolean | number | string };
export type AttributePayload<T> = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } }; export type AttributePayload<T> = { readonly [P in keyof T]: { readonly key: string; readonly value: T[P] } };
export abstract class XmlAttributeComponent<T extends object> extends BaseXmlComponent { // eslint-disable-next-line @typescript-eslint/no-explicit-any
export abstract class XmlAttributeComponent<T extends { readonly [key: string]: any }> extends BaseXmlComponent {
protected readonly xmlKeys?: AttributeMap<T>; protected readonly xmlKeys?: AttributeMap<T>;
public constructor(private readonly root: T) { public constructor(private readonly root: T) {
@ -16,12 +17,11 @@ export abstract class XmlAttributeComponent<T extends object> extends BaseXmlCom
public prepForXml(_: IContext): IXmlableObject { public prepForXml(_: IContext): IXmlableObject {
// eslint-disable-next-line functional/prefer-readonly-type // eslint-disable-next-line functional/prefer-readonly-type
const attrs: { [key: string]: string } = {}; const attrs: { [key: string]: string } = {};
Object.keys(this.root).forEach((key) => { Object.entries(this.root).forEach(([key, value]) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const value = (this.root as any)[key];
if (value !== undefined) { if (value !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const newKey = (this.xmlKeys && (this.xmlKeys as any)[key]) || key; const newKey = (this.xmlKeys && this.xmlKeys[key]) || key;
// eslint-disable-next-line functional/immutable-data // eslint-disable-next-line functional/immutable-data
attrs[newKey] = value; attrs[newKey] = value;
} }

View File

@ -17,7 +17,7 @@ import { appendRelationship, getNextRelationshipIndex } from "./relationship-man
import { appendContentType } from "./content-types-manager"; import { appendContentType } from "./content-types-manager";
// eslint-disable-next-line functional/prefer-readonly-type // eslint-disable-next-line functional/prefer-readonly-type
type InputDataType = Buffer | string | number[] | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream; export type InputDataType = Buffer | string | number[] | Uint8Array | ArrayBuffer | Blob | NodeJS.ReadableStream;
export const PatchType = { export const PatchType = {
DOCUMENT: "file", DOCUMENT: "file",

View File

@ -1 +1,2 @@
export * from "./from-docx"; export * from "./from-docx";
export * from "./patch-detector";

View File

@ -273,5 +273,65 @@ describe("paragraph-split-inject", () => {
}, },
}); });
}); });
it("should create an empty end element if it is at the end", () => {
const output = splitRunElement(
{
type: "element",
name: "w:r",
elements: [
{
type: "element",
name: "w:rPr",
elements: [
{ type: "element", name: "w:rFonts", attributes: { "w:eastAsia": "Times New Roman" } },
{ type: "element", name: "w:kern", attributes: { "w:val": "0" } },
{ type: "element", name: "w:sz", attributes: { "w:val": "20" } },
{
type: "element",
name: "w:lang",
attributes: { "w:val": "en-US", "w:eastAsia": "en-US", "w:bidi": "ar-SA" },
},
],
},
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
{ type: "element", name: "w:br" },
{ type: "element", name: "w:t", elements: [{ type: "text", text: "ɵ" }] },
],
},
"ɵ",
);
expect(output).to.deep.equal({
left: {
type: "element",
name: "w:r",
elements: [
{
type: "element",
name: "w:rPr",
elements: [
{ type: "element", name: "w:rFonts", attributes: { "w:eastAsia": "Times New Roman" } },
{ type: "element", name: "w:kern", attributes: { "w:val": "0" } },
{ type: "element", name: "w:sz", attributes: { "w:val": "20" } },
{
type: "element",
name: "w:lang",
attributes: { "w:val": "en-US", "w:eastAsia": "en-US", "w:bidi": "ar-SA" },
},
],
},
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
{ type: "element", name: "w:br" },
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
],
},
right: {
type: "element",
name: "w:r",
elements: [{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } }],
},
});
});
}); });
}); });

View File

@ -29,7 +29,7 @@ export const splitRunElement = (runElement: Element, token: string): { readonly
runElement.elements runElement.elements
?.map((e, i) => { ?.map((e, i) => {
if (e.type === "element" && e.name === "w:t") { if (e.type === "element" && e.name === "w:t") {
const text = (e.elements?.[0].text as string) ?? ""; const text = (e.elements?.[0]?.text as string) ?? "";
const splitText = text.split(token); const splitText = text.split(token);
const newElements = splitText.map((t) => ({ const newElements = splitText.map((t) => ({
...e, ...e,

View File

@ -0,0 +1,393 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import JSZip from "jszip";
import { patchDetector } from "./patch-detector";
const MOCK_XML = `
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex"
xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex"
xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex"
xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex"
xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex"
xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex"
xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex"
xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex"
xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink"
xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:oel="http://schemas.microsoft.com/office/2019/extlst"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex"
xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"
xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml"
xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash"
xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
<w:body>
<w:p w14:paraId="2499FE9F" w14:textId="0A3D130F" w:rsidR="00B51233"
w:rsidRDefault="007B52ED" w:rsidP="007B52ED">
<w:pPr>
<w:pStyle w:val="Title" />
</w:pPr>
<w:r>
<w:t>Hello World</w:t>
</w:r>
</w:p>
<w:p w14:paraId="6410D9A0" w14:textId="7579AB49" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED" />
<w:p w14:paraId="57ACF964" w14:textId="315D7A05" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED">
<w:r>
<w:t>Hello {{name}},</w:t>
</w:r>
<w:r w:rsidR="008126CB">
<w:t xml:space="preserve"> how are you?</w:t>
</w:r>
</w:p>
<w:p w14:paraId="38C7DF4A" w14:textId="66CDEC9A" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED" />
<w:p w14:paraId="04FABE2B" w14:textId="3DACA001" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED">
<w:r>
<w:t>{{paragraph_replace}}</w:t>
</w:r>
</w:p>
<w:p w14:paraId="7AD7975D" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
<w:p w14:paraId="3BD6D75A" w14:textId="19AE3121" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F">
<w:r>
<w:t>{{table}}</w:t>
</w:r>
</w:p>
<w:p w14:paraId="76023962" w14:textId="4E606AB9" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED" />
<w:tbl>
<w:tblPr>
<w:tblStyle w:val="TableGrid" />
<w:tblW w:w="0" w:type="auto" />
<w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1"
w:lastColumn="0" w:noHBand="0" w:noVBand="1" />
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="3003" />
<w:gridCol w:w="3003" />
<w:gridCol w:w="3004" />
</w:tblGrid>
<w:tr w:rsidR="00EF161F" w14:paraId="1DEC5955" w14:textId="77777777" w:rsidTr="00EF161F">
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="54DA5587" w14:textId="625BAC60" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F">
<w:r>
<w:t>{{table_heading_1}}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="57100910" w14:textId="71FD5616" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3004" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="1D388FAB" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
</w:tr>
<w:tr w:rsidR="00EF161F" w14:paraId="0F53D2DC" w14:textId="77777777" w:rsidTr="00EF161F">
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="0F2BCCED" w14:textId="3C3B6706" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F">
<w:r>
<w:t>Item: {{item_1}}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="1E6158AC" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3004" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="17937748" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
</w:tr>
<w:tr w:rsidR="00EF161F" w14:paraId="781DAC1A" w14:textId="77777777" w:rsidTr="00EF161F">
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="1DCD0343" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3003" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="5D02E3CD" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="3004" w:type="dxa" />
</w:tcPr>
<w:p w14:paraId="52EA0DBB" w14:textId="77777777" w:rsidR="00EF161F"
w:rsidRDefault="00EF161F" />
</w:tc>
</w:tr>
</w:tbl>
<w:p w14:paraId="47CD1FBC" w14:textId="23474CBC" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED" />
<w:p w14:paraId="0ACCEE90" w14:textId="67907499" w:rsidR="00EF161F"
w:rsidRDefault="0077578F">
<w:r>
<w:t>{{image_test}}</w:t>
</w:r>
</w:p>
<w:p w14:paraId="23FA9862" w14:textId="77777777" w:rsidR="0077578F"
w:rsidRDefault="0077578F" />
<w:p w14:paraId="01578F2F" w14:textId="3BDC6C85" w:rsidR="007B52ED"
w:rsidRDefault="007B52ED">
<w:r>
<w:t>Thank you</w:t>
</w:r>
</w:p>
<w:sectPr w:rsidR="007B52ED" w:rsidSect="0072043F">
<w:headerReference w:type="default" r:id="rId6" />
<w:footerReference w:type="default" r:id="rId7" />
<w:pgSz w:w="11900" w:h="16840" />
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708"
w:footer="708" w:gutter="0" />
<w:cols w:space="708" />
<w:docGrid w:linePitch="360" />
</w:sectPr>
</w:body>
</w:document>
`;
// cspell:disable
const MOCK_XML_2 = `
<w:body>
<w:tbl>
<w:tblPr>
<w:tblStyle w:val="TableGrid" />
<w:tblW w:w="9350" w:type="dxa" />
<w:jc w:val="left" />
<w:tblInd w:w="0" w:type="dxa" />
<w:tblLayout w:type="fixed" />
<w:tblCellMar>
<w:top w:w="0" w:type="dxa" />
<w:left w:w="108" w:type="dxa" />
<w:bottom w:w="0" w:type="dxa" />
<w:right w:w="108" w:type="dxa" />
</w:tblCellMar>
<w:tblLook w:firstRow="1" w:noVBand="1" w:lastRow="0" w:firstColumn="1"
w:lastColumn="0" w:noHBand="0" w:val="04a0" />
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="3119" />
<w:gridCol w:w="3141" />
<w:gridCol w:w="3090" />
</w:tblGrid>
<w:tr>
<w:trPr></w:trPr>
<w:tc>
<w:tcPr>
<w:tcW w:w="3119" w:type="dxa" />
<w:tcBorders>
<w:right w:val="nil" />
</w:tcBorders>
<w:shd w:color="auto" w:fill="D9D9D9" w:themeFill="background1"
w:themeFillShade="d9" w:val="clear" />
</w:tcPr>
<w:p>
<w:pPr>
<w:pStyle w:val="NormalSpaceAboveandBelow" />
<w:widowControl />
<w:spacing w:before="120" w:after="120" />
<w:jc w:val="left" />
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>{{</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>s</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>chool_</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>n</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>ame}}</w:t>
<w:br />
<w:t>{{</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>a</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>ddr</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>ess</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:eastAsia="Times New Roman" />
<w:kern w:val="0" />
<w:sz w:val="20" />
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
</w:rPr>
<w:t>}}</w:t>
<w:br />
<w:t>{{</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</w:tbl>
</w:body>
`;
// cspell:enable
describe("patch-detector", () => {
describe("patchDetector", () => {
describe("document.xml and [Content_Types].xml", () => {
beforeEach(() => {
vi.spyOn(JSZip, "loadAsync").mockReturnValue(
new Promise<JSZip>((resolve) => {
const zip = new JSZip();
zip.file("word/document.xml", MOCK_XML);
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
resolve(zip);
}),
);
});
afterEach(() => {
vi.restoreAllMocks();
});
it("should patch the document", async () => {
const output = await patchDetector({
data: Buffer.from(""),
});
expect(output).toMatchObject(["name", "paragraph_replace", "table", "image_test", "table_heading_1", "item_1"]);
});
});
});
describe("patchDetector", () => {
describe("document.xml and [Content_Types].xml", () => {
beforeEach(() => {
vi.spyOn(JSZip, "loadAsync").mockReturnValue(
new Promise<JSZip>((resolve) => {
const zip = new JSZip();
zip.file("word/document.xml", MOCK_XML_2);
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
resolve(zip);
}),
);
});
afterEach(() => {
vi.restoreAllMocks();
});
it("should patch the document", async () => {
const output = await patchDetector({
data: Buffer.from(""),
});
expect(output).toMatchObject(["school_name", "address"]);
});
});
});
});

View File

@ -0,0 +1,30 @@
import JSZip from "jszip";
import { toJson } from "./util";
import { traverse } from "./traverser";
import { InputDataType } from "./from-docx";
type PatchDetectorOptions = {
readonly data: InputDataType;
};
/** Detects which patches are needed/present in a template */
export const patchDetector = async ({ data }: PatchDetectorOptions): Promise<readonly string[]> => {
const zipContent = await JSZip.loadAsync(data);
const patches = new Set<string>();
for (const [key, value] of Object.entries(zipContent.files)) {
if (!key.endsWith(".xml") && !key.endsWith(".rels")) {
continue;
}
if (key.startsWith("word/") && !key.endsWith(".xml.rels")) {
const json = toJson(await value.async("text"));
traverse(json).forEach((p) => findPatchKeys(p.text).forEach((patch) => patches.add(patch)));
}
}
return Array.from(patches);
};
const findPatchKeys = (text: string): readonly string[] => {
const pattern = /(?<=\{\{).+?(?=\}\})/gs;
return text.match(pattern) ?? [];
};

View File

@ -200,5 +200,454 @@ describe("replacer", () => {
expect(JSON.stringify(output)).to.contain("Lorem ipsum paragraph"); expect(JSON.stringify(output)).to.contain("Lorem ipsum paragraph");
}); });
it("should replace", () => {
// cspell:disable
const output = replacer({
json: {
elements: [
{
type: "element",
name: "w:hdr",
elements: [
{
type: "element",
name: "w:p",
elements: [
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rPr",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rFonts",
attributes: { "w:eastAsia": "Times New Roman" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:kern",
attributes: { "w:val": "0" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:sz",
attributes: { "w:val": "20" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:lang",
attributes: {
"w:val": "en-US",
"w:eastAsia": "en-US",
"w:bidi": "ar-SA",
},
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "{{" }],
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rPr",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rFonts",
attributes: { "w:eastAsia": "Times New Roman" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:kern",
attributes: { "w:val": "0" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:sz",
attributes: { "w:val": "20" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:lang",
attributes: {
"w:val": "en-US",
"w:eastAsia": "en-US",
"w:bidi": "ar-SA",
},
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "s" }],
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rPr",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rFonts",
attributes: { "w:eastAsia": "Times New Roman" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:kern",
attributes: { "w:val": "0" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:sz",
attributes: { "w:val": "20" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:lang",
attributes: {
"w:val": "en-US",
"w:eastAsia": "en-US",
"w:bidi": "ar-SA",
},
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "chool_" }],
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rPr",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rFonts",
attributes: { "w:eastAsia": "Times New Roman" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:kern",
attributes: { "w:val": "0" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:sz",
attributes: { "w:val": "20" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:lang",
attributes: {
"w:val": "en-US",
"w:eastAsia": "en-US",
"w:bidi": "ar-SA",
},
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "n" }],
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "{{" }],
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rPr",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rFonts",
attributes: { "w:eastAsia": "Times New Roman" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:kern",
attributes: { "w:val": "0" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:sz",
attributes: { "w:val": "20" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:lang",
attributes: {
"w:val": "en-US",
"w:eastAsia": "en-US",
"w:bidi": "ar-SA",
},
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "a" }],
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rPr",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rFonts",
attributes: { "w:eastAsia": "Times New Roman" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:kern",
attributes: { "w:val": "0" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:sz",
attributes: { "w:val": "20" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:lang",
attributes: {
"w:val": "en-US",
"w:eastAsia": "en-US",
"w:bidi": "ar-SA",
},
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "ddr" }],
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rPr",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rFonts",
attributes: { "w:eastAsia": "Times New Roman" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:kern",
attributes: { "w:val": "0" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:sz",
attributes: { "w:val": "20" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:lang",
attributes: {
"w:val": "en-US",
"w:eastAsia": "en-US",
"w:bidi": "ar-SA",
},
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "ess" }],
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:r",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rPr",
elements: [
{ type: "text", text: "\n " },
{
type: "element",
name: "w:rFonts",
attributes: { "w:eastAsia": "Times New Roman" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:kern",
attributes: { "w:val": "0" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:sz",
attributes: { "w:val": "20" },
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:lang",
attributes: {
"w:val": "en-US",
"w:eastAsia": "en-US",
"w:bidi": "ar-SA",
},
},
{ type: "text", text: "\n " },
],
},
{ type: "text", text: "\n " },
{
type: "element",
name: "w:t",
elements: [{ type: "text", text: "}}" }],
},
],
},
],
},
],
},
],
},
// cspell:enable
patch: {
type: PatchType.PARAGRAPH,
children: [new Paragraph("Lorem ipsum paragraph")],
},
patchText: "{{address}}",
context: {
file: {} as unknown as File,
viewWrapper: {
Relationships: {},
} as unknown as IViewWrapper,
stack: [],
},
});
expect(JSON.stringify(output)).to.contain("Lorem ipsum paragraph");
});
}); });
}); });

View File

@ -67,7 +67,7 @@ export const replacer = ({
if (keepOriginalStyles) { if (keepOriginalStyles) {
const runElementNonTextualElements = runElementToBeReplaced.elements!.filter( const runElementNonTextualElements = runElementToBeReplaced.elements!.filter(
(e) => e.type === "element" && e.name !== "w:t", (e) => e.type === "element" && e.name !== "w:t" && e.name !== "w:br",
); );
newRunElements = textJson.map((e) => ({ newRunElements = textJson.map((e) => ({

View File

@ -15,7 +15,7 @@ const elementsToWrapper = (wrapper: ElementWrapper): readonly ElementWrapper[] =
parent: wrapper, parent: wrapper,
})) ?? []; })) ?? [];
export const findLocationOfText = (node: Element, text: string): readonly IRenderedParagraphNode[] => { export const traverse = (node: Element): readonly IRenderedParagraphNode[] => {
let renderedParagraphs: readonly IRenderedParagraphNode[] = []; let renderedParagraphs: readonly IRenderedParagraphNode[] = [];
// eslint-disable-next-line functional/prefer-readonly-type // eslint-disable-next-line functional/prefer-readonly-type
@ -41,5 +41,8 @@ export const findLocationOfText = (node: Element, text: string): readonly IRende
} }
} }
return renderedParagraphs.filter((p) => p.text.includes(text)); return renderedParagraphs;
}; };
export const findLocationOfText = (node: Element, text: string): readonly IRenderedParagraphNode[] =>
traverse(node).filter((p) => p.text.includes(text));

View File

@ -8,6 +8,7 @@ import {
convertMillimetersToTwip, convertMillimetersToTwip,
docPropertiesUniqueNumericIdGen, docPropertiesUniqueNumericIdGen,
uniqueId, uniqueId,
hashedId,
uniqueNumericIdCreator, uniqueNumericIdCreator,
uniqueUuid, uniqueUuid,
} from "./convenience-functions"; } from "./convenience-functions";
@ -72,6 +73,26 @@ describe("Utility", () => {
}); });
}); });
describe("#hashedId", () => {
it("should generate a hex string", () => {
expect(hashedId("")).to.equal("da39a3ee5e6b4b0d3255bfef95601890afd80709");
});
it("should work with string, Uint8Array, Buffer and ArrayBuffer", () => {
const stringInput = "DATA";
const uint8ArrayInput = new Uint8Array(new TextEncoder().encode(stringInput));
const bufferInput = Buffer.from(uint8ArrayInput);
const arrayBufferInput = uint8ArrayInput.buffer;
const expectedHash = "580393f5a94fb469585f5dd2a6859a4aab899f37";
expect(hashedId(stringInput)).to.equal(expectedHash);
expect(hashedId(uint8ArrayInput)).to.equal(expectedHash);
expect(hashedId(bufferInput)).to.equal(expectedHash);
expect(hashedId(arrayBufferInput)).to.equal(expectedHash);
});
});
describe("#uniqueUuid", () => { describe("#uniqueUuid", () => {
it("should generate a unique pseudorandom ID", () => { it("should generate a unique pseudorandom ID", () => {
expect(uniqueUuid()).to.not.be.empty; expect(uniqueUuid()).to.not.be.empty;

View File

@ -1,4 +1,5 @@
import { nanoid, customAlphabet } from "nanoid/non-secure"; import { nanoid, customAlphabet } from "nanoid/non-secure";
import hash from "hash.js";
// Twip - twentieths of a point // Twip - twentieths of a point
export const convertMillimetersToTwip = (millimeters: number): number => Math.floor((millimeters / 25.4) * 72 * 20); export const convertMillimetersToTwip = (millimeters: number): number => Math.floor((millimeters / 25.4) * 72 * 20);
@ -24,6 +25,12 @@ export const bookmarkUniqueNumericIdGen = (): UniqueNumericIdCreator => uniqueNu
export const uniqueId = (): string => nanoid().toLowerCase(); export const uniqueId = (): string => nanoid().toLowerCase();
export const hashedId = (data: Buffer | string | Uint8Array | ArrayBuffer): string =>
hash
.sha1()
.update(data instanceof ArrayBuffer ? new Uint8Array(data) : data)
.digest("hex");
const generateUuidPart = (count: number): string => customAlphabet("1234567890abcdef", count)(); const generateUuidPart = (count: number): string => customAlphabet("1234567890abcdef", count)();
export const uniqueUuid = (): string => export const uniqueUuid = (): string =>
`${generateUuidPart(8)}-${generateUuidPart(4)}-${generateUuidPart(4)}-${generateUuidPart(4)}-${generateUuidPart(12)}`; `${generateUuidPart(8)}-${generateUuidPart(4)}-${generateUuidPart(4)}-${generateUuidPart(4)}-${generateUuidPart(12)}`;

View File

@ -7,7 +7,9 @@ import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
tsconfigPaths(), tsconfigPaths(),
dts(), dts({
rollupTypes: true
}),
nodePolyfills({ nodePolyfills({
exclude: [], exclude: [],
globals: { globals: {