Compare commits
116 Commits
Author | SHA1 | Date | |
---|---|---|---|
3eb98533ae | |||
f5f021834e | |||
35702c3f77 | |||
8fbbd571ad | |||
2dd228be69 | |||
562835cfe7 | |||
6bac06e84d | |||
88436168ee | |||
0c3206d2e2 | |||
033debd339 | |||
d4f160732a | |||
4238fc9ab4 | |||
0d042b8dd1 | |||
afdd5f2d8f | |||
70c7b3d1b3 | |||
7b1cd5fe86 | |||
09ab169ffd | |||
35a82cf12e | |||
8d33cb01dd | |||
438d11dd86 | |||
d23b0d0789 | |||
1fa8c7ac82 | |||
3d6ead0359 | |||
379050dccd | |||
f9d1c197cf | |||
a89ee463e6 | |||
036caaacc8 | |||
80c37afe2b | |||
3a36d912fe | |||
c741d5d8ac | |||
0fa7dd13ad | |||
d5d80550e7 | |||
23a0a59454 | |||
066aa56f6a | |||
3997b7a5d6 | |||
ccc391607a | |||
9577192d41 | |||
968c3aed0f | |||
9c7a80729b | |||
5f26ca1c94 | |||
f2b1587bff | |||
3b9f80fb1a | |||
8da57bec51 | |||
63db9f290c | |||
e8bd4bd6c6 | |||
28d93b0c42 | |||
95f8e37006 | |||
87083cb264 | |||
1e8dc95c9c | |||
abfa242c28 | |||
56b5414152 | |||
8f46060be2 | |||
93b17ca2af | |||
66008115b8 | |||
55697a7c32 | |||
e9b259db6b | |||
55c51f7af1 | |||
4e7fd6a6dc | |||
6294ec448f | |||
668198b5d1 | |||
56b2ffe706 | |||
2ab06ffe36 | |||
0c51082bb9 | |||
9a9a2019f6 | |||
d94348f5ca | |||
4c10741862 | |||
222a25e4e2 | |||
5fd4490c4f | |||
e1cc65cb97 | |||
991f837bc1 | |||
87b3035465 | |||
4498305a6c | |||
cac7abba91 | |||
b9b55b2829 | |||
11365d5be5 | |||
47a5aff40c | |||
721fbbac67 | |||
bcca50fb40 | |||
229d2eb689 | |||
3ef80cc7b3 | |||
f1f11f36e4 | |||
28f187064e | |||
c391ca533a | |||
0ca92b80f9 | |||
84501b6038 | |||
dcd3e90b19 | |||
be7d84dfa0 | |||
b0d60109c9 | |||
b4659f9901 | |||
25f657b842 | |||
dfffb066f3 | |||
4f06d760a3 | |||
aea2531111 | |||
16707201b4 | |||
c7c9652095 | |||
05378f58ae | |||
d7549a1140 | |||
e3ee455186 | |||
210d9c58f2 | |||
db85c3dd2f | |||
414d0248f5 | |||
fa400bcf39 | |||
9e9ca526fe | |||
41c0fb5fc0 | |||
58e7dbf445 | |||
20e0213c7d | |||
b8f83fd6ad | |||
d1f75e3a42 | |||
337ff464cf | |||
cad4a5510b | |||
aa8438d8bd | |||
79363c2c2c | |||
f706d8e62d | |||
e7ee2a0fdf | |||
bd17df8e98 | |||
e237326319 |
@ -12,7 +12,6 @@
|
|||||||
[![Downloads per month][downloads-image]][downloads-url]
|
[![Downloads per month][downloads-image]][downloads-url]
|
||||||
[![GitHub Action Workflow Status][github-actions-workflow-image]][github-actions-workflow-url]
|
[![GitHub Action Workflow Status][github-actions-workflow-image]][github-actions-workflow-url]
|
||||||
[![Known Vulnerabilities][snky-image]][snky-url]
|
[![Known Vulnerabilities][snky-image]][snky-url]
|
||||||
[![Chat on Gitter][gitter-image]][gitter-url]
|
|
||||||
[![PRs Welcome][pr-image]][pr-url]
|
[![PRs Welcome][pr-image]][pr-url]
|
||||||
[![codecov][codecov-image]][codecov-url]
|
[![codecov][codecov-image]][codecov-url]
|
||||||
|
|
||||||
@ -107,8 +106,6 @@ Made with 💖
|
|||||||
[github-actions-workflow-url]: https://github.com/dolanmiu/docx/actions
|
[github-actions-workflow-url]: https://github.com/dolanmiu/docx/actions
|
||||||
[snky-image]: https://snyk.io/test/github/dolanmiu/docx/badge.svg
|
[snky-image]: https://snyk.io/test/github/dolanmiu/docx/badge.svg
|
||||||
[snky-url]: https://snyk.io/test/github/dolanmiu/docx
|
[snky-url]: https://snyk.io/test/github/dolanmiu/docx
|
||||||
[gitter-image]: https://badges.gitter.im/dolanmiu/docx.svg
|
|
||||||
[gitter-url]: https://gitter.im/docx-lib/Lobby
|
|
||||||
[pr-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
|
[pr-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
|
||||||
[pr-url]: http://makeapullrequest.com
|
[pr-url]: http://makeapullrequest.com
|
||||||
[codecov-image]: https://codecov.io/gh/dolanmiu/docx/branch/master/graph/badge.svg
|
[codecov-image]: https://codecov.io/gh/dolanmiu/docx/branch/master/graph/badge.svg
|
||||||
|
@ -43,6 +43,15 @@ const doc = new Document({
|
|||||||
color: "#FF0000",
|
color: "#FF0000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
document: {
|
||||||
|
run: {
|
||||||
|
size: "11pt",
|
||||||
|
font: "Calibri",
|
||||||
|
},
|
||||||
|
paragraph: {
|
||||||
|
alignment: AlignmentType.RIGHT,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
paragraphStyles: [
|
paragraphStyles: [
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,18 @@ import * as fs from "fs";
|
|||||||
import { Document, ExternalHyperlink, Footer, FootnoteReferenceRun, ImageRun, Packer, Paragraph, TextRun } from "docx";
|
import { Document, ExternalHyperlink, Footer, FootnoteReferenceRun, ImageRun, Packer, Paragraph, TextRun } from "docx";
|
||||||
|
|
||||||
const doc = new Document({
|
const doc = new Document({
|
||||||
|
styles: {
|
||||||
|
default: {
|
||||||
|
hyperlink: {
|
||||||
|
run: {
|
||||||
|
color: "FF0000",
|
||||||
|
underline: {
|
||||||
|
color: "0000FF",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
footnotes: {
|
footnotes: {
|
||||||
1: {
|
1: {
|
||||||
children: [
|
children: [
|
||||||
|
44
demo/90-check-boxes.ts
Normal file
44
demo/90-check-boxes.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Simple example to add check boxes to a document
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { Document, Packer, Paragraph, TextRun, CheckBox } from "docx";
|
||||||
|
|
||||||
|
const doc = new Document({
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
properties: {},
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun("Hello World"),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox(),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({ checked: true }),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({ checked: true, checkedState: { value: "2611" } }),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({ checked: true, checkedState: { value: "2611", font: "MS Gothic" } }),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({
|
||||||
|
checked: true,
|
||||||
|
checkedState: { value: "2611", font: "MS Gothic" },
|
||||||
|
uncheckedState: { value: "2610", font: "MS Gothic" },
|
||||||
|
}),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({
|
||||||
|
checked: true,
|
||||||
|
checkedState: { value: "2611", font: "MS Gothic" },
|
||||||
|
uncheckedState: { value: "2610", font: "MS Gothic" },
|
||||||
|
}),
|
||||||
|
new TextRun({ text: "Are you ok?", break: 1 }),
|
||||||
|
new CheckBox({ checked: true, alias: "Are you ok?" }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
Packer.toBuffer(doc).then((buffer) => {
|
||||||
|
fs.writeFileSync("My Document.docx", buffer);
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="../build/index.umd.cjs"></script>
|
<script src="../build/index.umd.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -5,9 +5,9 @@ import inquirer from "inquirer";
|
|||||||
import { $ } from "execa";
|
import { $ } from "execa";
|
||||||
|
|
||||||
export type Answers = {
|
export type Answers = {
|
||||||
type: "list" | "number";
|
readonly type: "list" | "number";
|
||||||
demoNumber?: number;
|
readonly demoNumber?: number;
|
||||||
demoFile?: number;
|
readonly demoFile?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dir = "./demo";
|
const dir = "./demo";
|
||||||
@ -15,8 +15,7 @@ const fileNames = fs.readdirSync(dir);
|
|||||||
|
|
||||||
const keys = fileNames.map((f) => path.parse(f).name);
|
const keys = fileNames.map((f) => path.parse(f).name);
|
||||||
const getFileNumber = (file: string): number => {
|
const getFileNumber = (file: string): number => {
|
||||||
const nameParts = file.split("-");
|
const [firstPart] = file.split("-");
|
||||||
const firstPart = nameParts[0];
|
|
||||||
|
|
||||||
return Number(firstPart);
|
return Number(firstPart);
|
||||||
};
|
};
|
||||||
@ -35,15 +34,15 @@ const answers = await inquirer.prompt<Answers>([
|
|||||||
name: "demoFile",
|
name: "demoFile",
|
||||||
message: "What demo do you wish to run?",
|
message: "What demo do you wish to run?",
|
||||||
choices: demoFiles,
|
choices: demoFiles,
|
||||||
filter: (input) => parseInt(input.split("-")[0]),
|
filter: (input) => parseInt(input.split("-")[0], 10),
|
||||||
when: (answers) => answers.type === "list",
|
when: (a) => a.type === "list",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "number",
|
type: "number",
|
||||||
name: "demoNumber",
|
name: "demoNumber",
|
||||||
message: "What demo do you wish to run? (Enter a number)",
|
message: "What demo do you wish to run? (Enter a number)",
|
||||||
default: 1,
|
default: 1,
|
||||||
when: (answers) => answers.type === "number",
|
when: (a) => a.type === "number",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -56,6 +55,7 @@ if (files.length === 0) {
|
|||||||
const filePath = path.join(dir, files[0]);
|
const filePath = path.join(dir, files[0]);
|
||||||
|
|
||||||
console.log(`Running demo ${demoNumber}: ${files[0]}`);
|
console.log(`Running demo ${demoNumber}: ${files[0]}`);
|
||||||
await $`ts-node --project demo/tsconfig.json ${filePath}`;
|
const { stdout } = await $`ts-node --project demo/tsconfig.json ${filePath}`;
|
||||||
|
console.log(stdout);
|
||||||
console.log("Successfully created document!");
|
console.log("Successfully created document!");
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Bullets and Numbering
|
# Bullets and Numbering
|
||||||
|
|
||||||
|
!> Bullets and Numbering requires an understanding of [Sections](usage/sections.md) and [Paragraphs](usage/paragraph.md).
|
||||||
|
|
||||||
`docx` is quite flexible in its bullets and numbering system, allowing
|
`docx` is quite flexible in its bullets and numbering system, allowing
|
||||||
the user great freedom in how bullets and numbers are to be styled and
|
the user great freedom in how bullets and numbers are to be styled and
|
||||||
displayed. E.g., numbers can be shown using Arabic numerals, roman
|
displayed. E.g., numbers can be shown using Arabic numerals, roman
|
||||||
@ -8,112 +10,128 @@ format also supports re-using bullets/numbering styles throughout the
|
|||||||
document, so that different lists using the same style need not
|
document, so that different lists using the same style need not
|
||||||
redefine them.
|
redefine them.
|
||||||
|
|
||||||
Because of this flexibility, bullets and numbering in DOCX involves a
|
## Configuration
|
||||||
couple of moving pieces:
|
|
||||||
|
|
||||||
1. Document-level bullets/numbering definitions (abstract)
|
Numbering is configured by adding config into `Document`:
|
||||||
2. Document-level bullets/numbering definitions (concrete)
|
|
||||||
3. Paragraph-level bullets/numbering selection
|
|
||||||
|
|
||||||
## Document-level bullets/numbering definitions (abstract)
|
|
||||||
|
|
||||||
Every document contains a set of abstract bullets/numbering
|
|
||||||
definitions which define the formatting and layout of paragraphs using
|
|
||||||
those bullets/numbering. An abstract numbering system defines how
|
|
||||||
bullets/numbers are to be shown for lists, including any sublists that
|
|
||||||
may be used. Thus each abstract definition includes a series of
|
|
||||||
_levels_ which form a sequence starting at 0 indicating the top-level
|
|
||||||
list look and increasing from there to describe the sublists, then
|
|
||||||
sub-sublists, etc. Each level includes the following properties:
|
|
||||||
|
|
||||||
* **level**: This is its 0-based index in the definition stack
|
|
||||||
* **numberFormat**: This indicates how the bullet or number should be
|
|
||||||
generated. Options include `bullet` (meaning don't count), `decimal`
|
|
||||||
(arabic numerals), `upperRoman`, `lowerRoman`, `hex`, and many
|
|
||||||
more.
|
|
||||||
* **levelText**: This is a format string using the output of the
|
|
||||||
`numberFormat` function and generating a string to insert before
|
|
||||||
every item in the list. You may use `%1`, `%2`, ... to reference the
|
|
||||||
numbers from each numbering level before this one. Thus a level
|
|
||||||
text of `%d)` with a number format of `lowerLetter` would result in
|
|
||||||
the sequence "a)", "b)", ...
|
|
||||||
* and a few others, which you can see in the OOXML spec section 17.9.6
|
|
||||||
|
|
||||||
## Document-level bullets/numbering definitions (concrete)
|
|
||||||
|
|
||||||
Concrete definitions are sort of like concrete subclasses of the
|
|
||||||
abstract definitions. They indicate their parent and are allowed to
|
|
||||||
override certain level definitions. Thus two lists that differ only in
|
|
||||||
how sub-sub-lists are to be displayed can share the same abstract
|
|
||||||
numbering definition and have slightly different concrete definitions.
|
|
||||||
|
|
||||||
## Paragraph-level bullets/numbering selection
|
|
||||||
|
|
||||||
In order to use a bullets/numbering definition (which must be
|
|
||||||
concrete), paragraphs need to select it, similar to applying a CSS
|
|
||||||
class to an element, using both the concrete numbering definition ID
|
|
||||||
and the level number that the paragraph should be at. Additionally, MS
|
|
||||||
Word and LibreOffice typically apply a "ListParagraph" style to
|
|
||||||
paragraphs that are being numbered.
|
|
||||||
|
|
||||||
## Using bullets/numbering in `docx`
|
|
||||||
|
|
||||||
`docx` includes a pre-defined bullet style which you can add to your
|
|
||||||
paragraphs using `para.bullets()`. If you require different bullet
|
|
||||||
styles or numbering of any kind, you'll have to use the
|
|
||||||
`docx.Numbering` class.
|
|
||||||
|
|
||||||
First you need to create a new numbering container class and use it to
|
|
||||||
create your abstract numbering style, define your levels, and create
|
|
||||||
your concrete numbering style:
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const numbering = new docx.Numbering();
|
new Document({
|
||||||
|
numbering: {
|
||||||
const abstractNum = numbering.createAbstractNumbering();
|
config: [...]
|
||||||
abstractNum.createLevel(0, "upperRoman", "%1", "start").addParagraphProperty(new Indent(720, 260));
|
}
|
||||||
abstractNum.createLevel(1, "decimal", "%2.", "start").addParagraphProperty(new Indent(1440, 980));
|
})
|
||||||
abstractNum.createLevel(2, "lowerLetter", "%3)", "start").addParagraphProperty(new Indent(2160, 1700));
|
|
||||||
|
|
||||||
const concrete = numbering.createConcreteNumbering(abstractNum);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then apply your concrete style to paragraphs using the
|
Each `config` entry includes the following properties:
|
||||||
`setNumbering` method:
|
|
||||||
|
. Each level includes the following properties:
|
||||||
|
|
||||||
|
| Property | Type | Notes | Possible Values |
|
||||||
|
| --------- | ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| reference | `string` | Required | A unique `string` |
|
||||||
|
| levels | `ILevelOptions[]` | Required | a series of _levels_ which form a sequence starting at 0 indicating the top-level list look and increasing from there to describe the sublists, then sub-sublists, etc |
|
||||||
|
|
||||||
|
### Level Options
|
||||||
|
|
||||||
|
Levels define the numbering definition itself, what it looks like, the indention, the alignment and the style. The reason why it is an array is because it allows the ability to create sub-lists. A sub list will have a different configuration because you may want the sub-list to have a different indentation or different bullet.
|
||||||
|
|
||||||
|
| Property | Type | Notes | Possible Values |
|
||||||
|
| --------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| level | `number` | Required | The list level this definition is for. `0` is for the root level, `1` is for a sub list, `2` is for a sub-sub-list etc. |
|
||||||
|
| format | `LevelFormat` | Optional | `DECIMAL`, `UPPER_ROMAN`, `LOWER_ROMAN`, `UPPER_LETTER`, `LOWER_LETTER`, `ORDINAL`, `CARDINAL_TEXT`, `ORDINAL_TEXT`, `HEX`, `CHICAGO`, `IDEOGRAPH__DIGITAL`, `JAPANESE_COUNTING`, `AIUEO`, `IROHA`, `DECIMAL_FULL_WIDTH`, `DECIMAL_HALF_WIDTH`, `JAPANESE_LEGAL`, `JAPANESE_DIGITAL_TEN_THOUSAND`, `DECIMAL_ENCLOSED_CIRCLE`, `DECIMAL_FULL_WIDTH2`, `AIUEO_FULL_WIDTH`, `IROHA_FULL_WIDTH`, `DECIMAL_ZERO`, `BULLET`, `GANADA`, `CHOSUNG`, `DECIMAL_ENCLOSED_FULLSTOP`, `DECIMAL_ENCLOSED_PARENTHESES`, `DECIMAL_ENCLOSED_CIRCLE_CHINESE`, `IDEOGRAPH_ENCLOSED_CIRCLE`, `IDEOGRAPH_TRADITIONAL`, `IDEOGRAPH_ZODIAC`, `IDEOGRAPH_ZODIAC_TRADITIONAL`, `TAIWANESE_COUNTING`, `IDEOGRAPH_LEGAL_TRADITIONAL`, `TAIWANESE_COUNTING_THOUSAND`, `TAIWANESE_DIGITAL`, `CHINESE_COUNTING`, `CHINESE_LEGAL_SIMPLIFIED`, `CHINESE_COUNTING_THOUSAND`, `KOREAN_DIGITAL`, `KOREAN_COUNTING`, `KOREAN_LEGAL`, `KOREAN_DIGITAL2`, `VIETNAMESE_COUNTING`, `RUSSIAN_LOWER`, `RUSSIAN_UPPER`, `NONE`, `NUMBER_IN_DASH`, `HEBREW1`, `HEBREW2`, `ARABIC_ALPHA`, `ARABIC_ABJAD`, `HINDI_VOWELS`, `HINDI_CONSONANTS`, `HINDI_NUMBERS`, `HINDI_COUNTING`, `THAI_LETTERS`, `THAI_NUMBERS`, `THAI_COUNTING`, `BAHT_TEXT`, `DOLLAR_TEXT`, `CUSTOM` |
|
||||||
|
| text | `string` | Optional | A unique `string` to describe the shape of the bullet |
|
||||||
|
| alignment | `string` | Required | `START`, `CENTER`, `END`, `BOTH`, `MEDIUM_KASHIDA`, `DISTRIBUTE`, `NUM_TAB`, `HIGH_KASHIDA`, `LOW_KASHIDA`, `THAI_DISTRIBUTE`, `LEFT`, `RIGHT`, `JUSTIFIED` |
|
||||||
|
| style | `string` | Optional | [Sections](usage/styling-with-js.md) |
|
||||||
|
|
||||||
|
## Using ordered lists in `docx`
|
||||||
|
|
||||||
|
Add a `numbering` section to the `Document` to numbering style, define your levels. Use `LevelFormat.UPPER_ROMAN` for the `format` in `levels`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
topLevelP.setNumbering(concrete, 0);
|
const doc = new Document({
|
||||||
subP.setNumbering(concrete, 1);
|
...
|
||||||
subSubP.setNumbering(concrete, 2);
|
numbering: {
|
||||||
```
|
config: [
|
||||||
|
{
|
||||||
## Unindent numbering
|
reference: "my-numbering",
|
||||||
|
levels: [
|
||||||
Default:1. test
|
{
|
||||||
|
|
||||||
After:1.test
|
|
||||||
|
|
||||||
Use default numbering have indent,If you want unindent numbering
|
|
||||||
|
|
||||||
How to custom number see the demo:
|
|
||||||
https://runkit.com/dolanmiu/docx-demo3
|
|
||||||
|
|
||||||
```ts
|
|
||||||
|
|
||||||
enum LevelSuffix {
|
|
||||||
NOTHING = "nothing",
|
|
||||||
SPACE = "space",
|
|
||||||
TAB = "tab"
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom numbering
|
|
||||||
const levels=[
|
|
||||||
{
|
|
||||||
level: 0,
|
level: 0,
|
||||||
format: "decimal",
|
format: LevelFormat.UPPER_ROMAN,
|
||||||
text: "%1.",
|
text: "%1",
|
||||||
alignment: AlignmentType.START,
|
alignment: AlignmentType.START,
|
||||||
suffix: LevelSuffix.NOTHING, // Cancel intent
|
style: {
|
||||||
}]
|
paragraph: {
|
||||||
|
indent: { left: 2880, hanging: 2420 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
And then on a `Paragraph`, we can add use the numbering created:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
new Paragraph({
|
||||||
|
text: "Hey you!",
|
||||||
|
numbering: {
|
||||||
|
reference: "my-numbering",
|
||||||
|
level: 0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
|
## Un-ordered lists / Bullet points
|
||||||
|
|
||||||
|
Add a `numbering` section to the `Document` to numbering style, define your levels. Use `LevelFormat.BULLET` for the `format` in `levels`:
|
||||||
|
|
||||||
|
```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) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
And then on a `Paragraph`, we can add use the numbering created:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
new Paragraph({
|
||||||
|
text: "Hey you!",
|
||||||
|
numbering: {
|
||||||
|
reference: "my-bullet-points",
|
||||||
|
level: 0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
```
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
[Example](https://raw.githubusercontent.com/dolanmiu/docx/master/demo/3-numbering-and-bullet-points.ts ":include")
|
||||||
|
|
||||||
|
_Source: https://github.com/dolanmiu/docx/blob/master/demo/3-numbering-and-bullet-points.ts_
|
||||||
|
@ -9,11 +9,16 @@
|
|||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
*Note*: Font and color selection from the theme are currently not supported.
|
||||||
|
|
||||||
3. You can even create a totally new `Style`:
|
3. You can even create a totally new `Style`:
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
*Note*: When selecting the style type, it is important to consider the component being used.
|
||||||
|
|
||||||
|
|
||||||
4. Save
|
4. Save
|
||||||
5. Re-name the saved `.docx` file to `.zip` and un-zip
|
5. Re-name the saved `.docx` file to `.zip` and un-zip
|
||||||
6. Find `styles.xml`
|
6. Find `styles.xml`
|
||||||
|
5039
package-lock.json
generated
5039
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
37
package.json
37
package.json
@ -1,20 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "docx",
|
"name": "docx",
|
||||||
"version": "8.1.0",
|
"version": "8.2.4",
|
||||||
"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.cjs",
|
"main": "build/index.umd.js",
|
||||||
"module": "./build/index.js",
|
"module": "./build/index.mjs",
|
||||||
"types": "./build/index.d.ts",
|
"types": "./build/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"browser": {
|
|
||||||
"default": "./build/index.umd.cjs"
|
|
||||||
},
|
|
||||||
"require": "./build/index.cjs",
|
"require": "./build/index.cjs",
|
||||||
"types": "./build/index.d.ts",
|
"types": "./build/index.d.ts",
|
||||||
"import": "./build/index.js",
|
"import": "./build/index.mjs",
|
||||||
"default": "./build/index.js"
|
"default": "./build/index.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@ -58,7 +55,6 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.3.1",
|
||||||
"fflate": "^0.8.0",
|
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"xml": "^1.0.1",
|
"xml": "^1.0.1",
|
||||||
@ -71,16 +67,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://docx.js.org",
|
"homepage": "https://docx.js.org",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild/win32-x64": "^0.18.3",
|
|
||||||
"@types/inquirer": "^9.0.3",
|
"@types/inquirer": "^9.0.3",
|
||||||
"@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": "^5.36.1",
|
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
||||||
"@typescript-eslint/parser": "^5.36.1",
|
"@typescript-eslint/parser": "^6.9.1",
|
||||||
"@vitest/coverage-v8": "^0.32.0",
|
"@vitest/coverage-v8": "^0.33.0",
|
||||||
"@vitest/ui": "^0.32.0",
|
"@vitest/ui": "^0.33.0",
|
||||||
"cspell": "^6.2.2",
|
"cspell": "^7.3.8",
|
||||||
"docsify-cli": "^4.3.0",
|
"docsify-cli": "^4.3.0",
|
||||||
"eslint": "^8.23.0",
|
"eslint": "^8.23.0",
|
||||||
"eslint-plugin-functional": "^5.0.8",
|
"eslint-plugin-functional": "^5.0.8",
|
||||||
@ -88,23 +83,23 @@
|
|||||||
"eslint-plugin-jsdoc": "^46.2.6",
|
"eslint-plugin-jsdoc": "^46.2.6",
|
||||||
"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": "^47.0.0",
|
"eslint-plugin-unicorn": "^48.0.1",
|
||||||
"execa": "^7.1.1",
|
"execa": "^8.0.1",
|
||||||
"glob": "^10.2.7",
|
"glob": "^10.2.7",
|
||||||
"inquirer": "^9.2.7",
|
"inquirer": "^9.2.7",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"pre-commit": "^1.2.2",
|
"pre-commit": "^1.2.2",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^3.0.0",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
"tsconfig-paths": "^4.0.0",
|
"tsconfig-paths": "^4.0.0",
|
||||||
"typedoc": "^0.24.8",
|
"typedoc": "^0.24.8",
|
||||||
"typescript": "5.1.3",
|
"typescript": "5.1.6",
|
||||||
"unzipper": "^0.10.11",
|
"unzipper": "^0.10.11",
|
||||||
"vite": "^4.3.2",
|
"vite": "^4.3.2",
|
||||||
"vite-plugin-dts": "^2.3.0",
|
"vite-plugin-dts": "^3.3.1",
|
||||||
"vite-plugin-node-polyfills": "^0.9.0",
|
"vite-plugin-node-polyfills": "^0.9.0",
|
||||||
"vite-tsconfig-paths": "^4.2.0",
|
"vite-tsconfig-paths": "^4.2.0",
|
||||||
"vitest": "^0.32.0"
|
"vitest": "^0.33.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
|
29
src/file/checkbox/checkbox-symbol.ts
Normal file
29
src/file/checkbox/checkbox-symbol.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// This represents element type CT_SdtCheckboxSymbol element
|
||||||
|
// <xsd:complexType name="CT_SdtCheckboxSymbol">
|
||||||
|
// <xsd:attribute name="font" type="w:ST_String"/>
|
||||||
|
// <xsd:attribute name="val" type="w:ST_ShortHexNumber"/>
|
||||||
|
// </xsd:complexType>
|
||||||
|
|
||||||
|
import { XmlAttributeComponent, XmlComponent } from "@file/xml-components";
|
||||||
|
import { shortHexNumber } from "@util/values";
|
||||||
|
|
||||||
|
class CheckboxSymbolAttributes extends XmlAttributeComponent<{
|
||||||
|
readonly val?: string | number | boolean;
|
||||||
|
readonly symbolfont?: string;
|
||||||
|
}> {
|
||||||
|
protected readonly xmlKeys = {
|
||||||
|
val: "w14:val",
|
||||||
|
symbolfont: "w14:font",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CheckBoxSymbolElement extends XmlComponent {
|
||||||
|
public constructor(name: string, val: string, font?: string) {
|
||||||
|
super(name);
|
||||||
|
if (font) {
|
||||||
|
this.root.push(new CheckboxSymbolAttributes({ val: shortHexNumber(val), symbolfont: font }));
|
||||||
|
} else {
|
||||||
|
this.root.push(new CheckboxSymbolAttributes({ val }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
src/file/checkbox/checkbox-util.spec.ts
Normal file
85
src/file/checkbox/checkbox-util.spec.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { Formatter } from "@export/formatter";
|
||||||
|
import { CheckBoxUtil } from ".";
|
||||||
|
|
||||||
|
describe("CheckBoxUtil", () => {
|
||||||
|
describe("#constructor()", () => {
|
||||||
|
it("should create a CheckBoxUtil with proper root and default values", () => {
|
||||||
|
const checkBoxUtil = new CheckBoxUtil();
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBoxUtil);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "MS Gothic",
|
||||||
|
"w14:val": "2612",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "MS Gothic",
|
||||||
|
"w14:val": "2610",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a CheckBoxUtil with proper structure and custom values", () => {
|
||||||
|
const checkBoxUtil = new CheckBoxUtil({
|
||||||
|
checked: true,
|
||||||
|
checkedState: {
|
||||||
|
value: "2713",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
uncheckedState: {
|
||||||
|
value: "2705",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBoxUtil);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2713",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2705",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
45
src/file/checkbox/checkbox-util.ts
Normal file
45
src/file/checkbox/checkbox-util.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// <xsd:complexType name="CT_SdtCheckbox">
|
||||||
|
// <xsd:sequence>
|
||||||
|
// <xsd:element name="checked" type="CT_OnOff" minOccurs="0"/>
|
||||||
|
// <xsd:element name="checkedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
||||||
|
// <xsd:element name="uncheckedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
||||||
|
// </xsd:sequence>
|
||||||
|
// </xsd:complexType>
|
||||||
|
// <xsd:element name="checkbox" type="CT_SdtCheckbox"/>
|
||||||
|
|
||||||
|
import { XmlComponent } from "@file/xml-components";
|
||||||
|
import { CheckBoxSymbolElement } from "@file/checkbox/checkbox-symbol";
|
||||||
|
|
||||||
|
export interface ICheckboxSymbolProperties {
|
||||||
|
readonly value?: string;
|
||||||
|
readonly font?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICheckboxSymbolOptions {
|
||||||
|
readonly alias?: string;
|
||||||
|
readonly checked?: boolean;
|
||||||
|
readonly checkedState?: ICheckboxSymbolProperties;
|
||||||
|
readonly uncheckedState?: ICheckboxSymbolProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CheckBoxUtil extends XmlComponent {
|
||||||
|
private readonly DEFAULT_UNCHECKED_SYMBOL: string = "2610";
|
||||||
|
private readonly DEFAULT_CHECKED_SYMBOL: string = "2612";
|
||||||
|
private readonly DEFAULT_FONT: string = "MS Gothic";
|
||||||
|
public constructor(options?: ICheckboxSymbolOptions) {
|
||||||
|
super("w14:checkbox");
|
||||||
|
|
||||||
|
const value = options?.checked ? "1" : "0";
|
||||||
|
let symbol: string;
|
||||||
|
let font: string;
|
||||||
|
this.root.push(new CheckBoxSymbolElement("w14:checked", value));
|
||||||
|
|
||||||
|
symbol = options?.checkedState?.value ? options?.checkedState?.value : this.DEFAULT_CHECKED_SYMBOL;
|
||||||
|
font = options?.checkedState?.font ? options?.checkedState?.font : this.DEFAULT_FONT;
|
||||||
|
this.root.push(new CheckBoxSymbolElement("w14:checkedState", symbol, font));
|
||||||
|
|
||||||
|
symbol = options?.uncheckedState?.value ? options?.uncheckedState?.value : this.DEFAULT_UNCHECKED_SYMBOL;
|
||||||
|
font = options?.uncheckedState?.font ? options?.uncheckedState?.font : this.DEFAULT_FONT;
|
||||||
|
this.root.push(new CheckBoxSymbolElement("w14:uncheckedState", symbol, font));
|
||||||
|
}
|
||||||
|
}
|
213
src/file/checkbox/checkbox.spec.ts
Normal file
213
src/file/checkbox/checkbox.spec.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { Formatter } from "@export/formatter";
|
||||||
|
|
||||||
|
import { CheckBox } from "./checkbox";
|
||||||
|
|
||||||
|
describe("CheckBox", () => {
|
||||||
|
describe("#constructor()", () => {
|
||||||
|
it("should create a CheckBox with proper root and default values (no alias, no custom state)", () => {
|
||||||
|
const checkBox = new CheckBox();
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBox);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:sdt": [
|
||||||
|
{
|
||||||
|
"w:sdtPr": [
|
||||||
|
{
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "MS Gothic",
|
||||||
|
"w14:val": "2612",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "MS Gothic",
|
||||||
|
"w14:val": "2610",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:sdtContent": [
|
||||||
|
{
|
||||||
|
"w:r": [
|
||||||
|
{
|
||||||
|
"w:sym": {
|
||||||
|
_attr: {
|
||||||
|
"w:char": "2610",
|
||||||
|
"w:font": "MS Gothic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["2713", "Segoe UI Symbol", "2713", "Segoe UI Symbol"],
|
||||||
|
[undefined, undefined, "2612", "MS Gothic"],
|
||||||
|
])("should create a CheckBox with proper root and custom values", (inputChar, inputFont, actualChar, actualFont) => {
|
||||||
|
const checkBox = new CheckBox({
|
||||||
|
alias: "Custom Checkbox",
|
||||||
|
checked: true,
|
||||||
|
checkedState: {
|
||||||
|
value: inputChar,
|
||||||
|
font: inputFont,
|
||||||
|
},
|
||||||
|
uncheckedState: {
|
||||||
|
value: "2705",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBox);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:sdt": [
|
||||||
|
{
|
||||||
|
"w:sdtPr": [
|
||||||
|
{
|
||||||
|
"w:alias": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": "Custom Checkbox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": actualFont,
|
||||||
|
"w14:val": actualChar,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2705",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:sdtContent": [
|
||||||
|
{
|
||||||
|
"w:r": [
|
||||||
|
{
|
||||||
|
"w:sym": {
|
||||||
|
_attr: {
|
||||||
|
"w:char": actualChar,
|
||||||
|
"w:font": actualFont,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a CheckBox with proper root, custom state, and no alias", () => {
|
||||||
|
const checkBox = new CheckBox({
|
||||||
|
checked: false,
|
||||||
|
checkedState: {
|
||||||
|
value: "2713",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
uncheckedState: {
|
||||||
|
value: "2705",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBox);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:sdt": [
|
||||||
|
{
|
||||||
|
"w:sdtPr": [
|
||||||
|
{
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2713",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2705",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:sdtContent": [
|
||||||
|
{
|
||||||
|
"w:r": [
|
||||||
|
{
|
||||||
|
"w:sym": {
|
||||||
|
_attr: {
|
||||||
|
"w:char": "2705",
|
||||||
|
"w:font": "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
43
src/file/checkbox/checkbox.ts
Normal file
43
src/file/checkbox/checkbox.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { SymbolRun } from "@file/paragraph/run/symbol-run";
|
||||||
|
import { StructuredDocumentTagProperties } from "@file/table-of-contents/sdt-properties";
|
||||||
|
import { StructuredDocumentTagContent } from "@file/table-of-contents/sdt-content";
|
||||||
|
import { XmlComponent } from "@file/xml-components";
|
||||||
|
import { CheckBoxUtil, ICheckboxSymbolOptions } from "./checkbox-util";
|
||||||
|
|
||||||
|
export class CheckBox extends XmlComponent {
|
||||||
|
// default values per Microsoft
|
||||||
|
private readonly DEFAULT_UNCHECKED_SYMBOL: string = "2610";
|
||||||
|
private readonly DEFAULT_CHECKED_SYMBOL: string = "2612";
|
||||||
|
private readonly DEFAULT_FONT: string = "MS Gothic";
|
||||||
|
public constructor(options?: ICheckboxSymbolOptions) {
|
||||||
|
super("w:sdt");
|
||||||
|
|
||||||
|
const properties = new StructuredDocumentTagProperties(options?.alias);
|
||||||
|
properties.addChildElement(new CheckBoxUtil(options));
|
||||||
|
this.root.push(properties);
|
||||||
|
|
||||||
|
const content = new StructuredDocumentTagContent();
|
||||||
|
const checkedFont: string | undefined = options?.checkedState?.font;
|
||||||
|
const checkedText: string | undefined = options?.checkedState?.value;
|
||||||
|
const uncheckedFont: string | undefined = options?.uncheckedState?.font;
|
||||||
|
const uncheckedText: string | undefined = options?.uncheckedState?.value;
|
||||||
|
let symbolFont: string;
|
||||||
|
let char: string;
|
||||||
|
|
||||||
|
if (options?.checked) {
|
||||||
|
symbolFont = checkedFont ? checkedFont : this.DEFAULT_FONT;
|
||||||
|
char = checkedText ? checkedText : this.DEFAULT_CHECKED_SYMBOL;
|
||||||
|
} else {
|
||||||
|
symbolFont = uncheckedFont ? uncheckedFont : this.DEFAULT_FONT;
|
||||||
|
char = uncheckedText ? uncheckedText : this.DEFAULT_UNCHECKED_SYMBOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialRenderedChar = new SymbolRun({
|
||||||
|
char: char,
|
||||||
|
symbolfont: symbolFont,
|
||||||
|
});
|
||||||
|
|
||||||
|
content.addChildElement(initialRenderedChar);
|
||||||
|
this.root.push(content);
|
||||||
|
}
|
||||||
|
}
|
3
src/file/checkbox/index.ts
Normal file
3
src/file/checkbox/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./checkbox-util";
|
||||||
|
export * from "./checkbox-symbol";
|
||||||
|
export * from "./checkbox";
|
@ -5,7 +5,6 @@ import { FooterWrapper } from "@file/footer-wrapper";
|
|||||||
import { HeaderWrapper } from "@file/header-wrapper";
|
import { HeaderWrapper } from "@file/header-wrapper";
|
||||||
import { VerticalAlign, VerticalAlignElement } from "@file/vertical-align";
|
import { VerticalAlign, VerticalAlignElement } from "@file/vertical-align";
|
||||||
import { OnOffElement, XmlComponent } from "@file/xml-components";
|
import { OnOffElement, XmlComponent } from "@file/xml-components";
|
||||||
import { PositiveUniversalMeasure, UniversalMeasure } from "@util/values";
|
|
||||||
|
|
||||||
import { HeaderFooterReference, HeaderFooterReferenceType, HeaderFooterType } from "./properties/header-footer-reference";
|
import { HeaderFooterReference, HeaderFooterReferenceType, HeaderFooterType } from "./properties/header-footer-reference";
|
||||||
import { Columns, IColumnsAttributes } from "./properties/columns";
|
import { Columns, IColumnsAttributes } from "./properties/columns";
|
||||||
@ -76,10 +75,10 @@ export interface ISectionPropertiesOptions {
|
|||||||
// </xsd:group>
|
// </xsd:group>
|
||||||
|
|
||||||
export const sectionMarginDefaults = {
|
export const sectionMarginDefaults = {
|
||||||
TOP: "1in" as UniversalMeasure,
|
TOP: 1440,
|
||||||
RIGHT: "1in" as PositiveUniversalMeasure,
|
RIGHT: 1440,
|
||||||
BOTTOM: "1in" as UniversalMeasure,
|
BOTTOM: 1440,
|
||||||
LEFT: "1in" as PositiveUniversalMeasure,
|
LEFT: 1440,
|
||||||
HEADER: 708,
|
HEADER: 708,
|
||||||
FOOTER: 708,
|
FOOTER: 708,
|
||||||
GUTTER: 0,
|
GUTTER: 0,
|
||||||
|
@ -80,6 +80,7 @@ export class Document extends XmlComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public add(item: Paragraph | Table | TableOfContents | ConcreteHyperlink): Document {
|
public add(item: Paragraph | Table | TableOfContents | ConcreteHyperlink): Document {
|
||||||
|
// eslint-disable-next-line functional/immutable-data
|
||||||
this.body.push(item);
|
this.body.push(item);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,11 @@ export class FooterWrapper implements IViewWrapper {
|
|||||||
private readonly footer: Footer;
|
private readonly footer: Footer;
|
||||||
private readonly relationships: Relationships;
|
private readonly relationships: Relationships;
|
||||||
|
|
||||||
public constructor(private readonly media: Media, referenceId: number, initContent?: XmlComponent) {
|
public constructor(
|
||||||
|
private readonly media: Media,
|
||||||
|
referenceId: number,
|
||||||
|
initContent?: XmlComponent,
|
||||||
|
) {
|
||||||
this.footer = new Footer(referenceId, initContent);
|
this.footer = new Footer(referenceId, initContent);
|
||||||
this.relationships = new Relationships();
|
this.relationships = new Relationships();
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,11 @@ export class HeaderWrapper implements IViewWrapper {
|
|||||||
private readonly header: Header;
|
private readonly header: Header;
|
||||||
private readonly relationships: Relationships;
|
private readonly relationships: Relationships;
|
||||||
|
|
||||||
public constructor(private readonly media: Media, referenceId: number, initContent?: XmlComponent) {
|
public constructor(
|
||||||
|
private readonly media: Media,
|
||||||
|
referenceId: number,
|
||||||
|
initContent?: XmlComponent,
|
||||||
|
) {
|
||||||
this.header = new Header(referenceId, initContent);
|
this.header = new Header(referenceId, initContent);
|
||||||
this.relationships = new Relationships();
|
this.relationships = new Relationships();
|
||||||
}
|
}
|
||||||
|
@ -17,3 +17,4 @@ export * from "./track-revision";
|
|||||||
export * from "./shared";
|
export * from "./shared";
|
||||||
export * from "./border";
|
export * from "./border";
|
||||||
export * from "./vertical-align";
|
export * from "./vertical-align";
|
||||||
|
export * from "./checkbox";
|
||||||
|
@ -216,7 +216,7 @@ export class Numbering extends XmlComponent {
|
|||||||
abstractNumId: abstractNumbering.id,
|
abstractNumId: abstractNumbering.id,
|
||||||
reference,
|
reference,
|
||||||
instance,
|
instance,
|
||||||
overrideLevel:
|
overrideLevels: [
|
||||||
firstLevelStartNumber && Number.isInteger(firstLevelStartNumber)
|
firstLevelStartNumber && Number.isInteger(firstLevelStartNumber)
|
||||||
? {
|
? {
|
||||||
num: 0,
|
num: 0,
|
||||||
@ -226,6 +226,7 @@ export class Numbering extends XmlComponent {
|
|||||||
num: 0,
|
num: 0,
|
||||||
start: 1,
|
start: 1,
|
||||||
},
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.concreteNumberingMap.set(fullReference, new ConcreteNumbering(concreteNumberingSettings));
|
this.concreteNumberingMap.set(fullReference, new ConcreteNumbering(concreteNumberingSettings));
|
||||||
|
@ -6,6 +6,7 @@ import { FileChild } from "@file/file-child";
|
|||||||
|
|
||||||
import { TargetModeType } from "../relationships/relationship/relationship";
|
import { TargetModeType } from "../relationships/relationship/relationship";
|
||||||
import { DeletedTextRun, InsertedTextRun } from "../track-revision";
|
import { DeletedTextRun, InsertedTextRun } from "../track-revision";
|
||||||
|
import { CheckBox } from "../checkbox";
|
||||||
import { ColumnBreak, PageBreak } from "./formatting/break";
|
import { ColumnBreak, PageBreak } from "./formatting/break";
|
||||||
import { Bookmark, ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } from "./links";
|
import { Bookmark, ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } from "./links";
|
||||||
import { Math } from "./math";
|
import { Math } from "./math";
|
||||||
@ -33,7 +34,8 @@ export type ParagraphChild =
|
|||||||
| Comment
|
| Comment
|
||||||
| CommentRangeStart
|
| CommentRangeStart
|
||||||
| CommentRangeEnd
|
| CommentRangeEnd
|
||||||
| CommentReference;
|
| CommentReference
|
||||||
|
| CheckBox;
|
||||||
|
|
||||||
export interface IParagraphOptions extends IParagraphPropertiesOptions {
|
export interface IParagraphOptions extends IParagraphPropertiesOptions {
|
||||||
readonly text?: string;
|
readonly text?: string;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// http://officeopenxml.com/WPparagraphProperties.php
|
// http://officeopenxml.com/WPparagraphProperties.php
|
||||||
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_suppressLineNumbers_topic_ID0ECJAO.html
|
// https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_suppressLineNumbers_topic_ID0ECJAO.html
|
||||||
|
/* eslint-disable functional/immutable-data */
|
||||||
import { IContext, IgnoreIfEmptyXmlComponent, IXmlableObject, OnOffElement, XmlComponent } from "@file/xml-components";
|
import { IContext, IgnoreIfEmptyXmlComponent, IXmlableObject, OnOffElement, XmlComponent } from "@file/xml-components";
|
||||||
import { DocumentWrapper } from "../document-wrapper";
|
import { DocumentWrapper } from "../document-wrapper";
|
||||||
import { IShadingAttributesProperties, Shading } from "../shading";
|
import { IShadingAttributesProperties, Shading } from "../shading";
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// https://www.ecma-international.org/wp-content/uploads/ECMA-376-1_5th_edition_december_2016.zip page 297, section 17.3.2.21
|
// https://www.ecma-international.org/wp-content/uploads/ECMA-376-1_5th_edition_december_2016.zip page 297, section 17.3.2.21
|
||||||
|
/* eslint-disable functional/immutable-data */
|
||||||
import { BorderElement, IBorderOptions } from "@file/border";
|
import { BorderElement, IBorderOptions } from "@file/border";
|
||||||
import { IShadingAttributesProperties, Shading } from "@file/shading";
|
import { IShadingAttributesProperties, Shading } from "@file/shading";
|
||||||
import { ChangeAttributes, IChangedAttributesProperties } from "@file/track-revision/track-revision";
|
import { ChangeAttributes, IChangedAttributesProperties } from "@file/track-revision/track-revision";
|
||||||
|
@ -318,4 +318,45 @@ describe("Default Styles", () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("HyperlinkStyle#constructor", () => {
|
||||||
|
const style = new defaultStyles.HyperlinkStyle({
|
||||||
|
run: {
|
||||||
|
color: "FF0000",
|
||||||
|
underline: {
|
||||||
|
color: "0000FF",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const tree = new Formatter().format(style);
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:style": [
|
||||||
|
{ _attr: { "w:type": "character", "w:styleId": "Hyperlink" } },
|
||||||
|
{ "w:name": { _attr: { "w:val": "Hyperlink" } } },
|
||||||
|
{ "w:basedOn": { _attr: { "w:val": "DefaultParagraphFont" } } },
|
||||||
|
{
|
||||||
|
"w:uiPriority": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": 99,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:unhideWhenUsed": EMPTY_OBJECT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:rPr": [
|
||||||
|
{ "w:u": { _attr: { "w:color": "0000FF", "w:val": "single" } } },
|
||||||
|
{
|
||||||
|
"w:color": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": "FF0000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -8,10 +8,10 @@ import { IBaseParagraphStyleOptions, IParagraphStyleOptions, StyleForParagraph }
|
|||||||
export class HeadingStyle extends StyleForParagraph {
|
export class HeadingStyle extends StyleForParagraph {
|
||||||
public constructor(options: IParagraphStyleOptions) {
|
public constructor(options: IParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
basedOn: "Normal",
|
basedOn: "Normal",
|
||||||
next: "Normal",
|
next: "Normal",
|
||||||
quickFormat: true,
|
quickFormat: true,
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,9 +19,9 @@ export class HeadingStyle extends StyleForParagraph {
|
|||||||
export class TitleStyle extends HeadingStyle {
|
export class TitleStyle extends HeadingStyle {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Title",
|
id: "Title",
|
||||||
name: "Title",
|
name: "Title",
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,9 +29,9 @@ export class TitleStyle extends HeadingStyle {
|
|||||||
export class Heading1Style extends HeadingStyle {
|
export class Heading1Style extends HeadingStyle {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Heading1",
|
id: "Heading1",
|
||||||
name: "Heading 1",
|
name: "Heading 1",
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,9 +39,9 @@ export class Heading1Style extends HeadingStyle {
|
|||||||
export class Heading2Style extends HeadingStyle {
|
export class Heading2Style extends HeadingStyle {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Heading2",
|
id: "Heading2",
|
||||||
name: "Heading 2",
|
name: "Heading 2",
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,9 +49,9 @@ export class Heading2Style extends HeadingStyle {
|
|||||||
export class Heading3Style extends HeadingStyle {
|
export class Heading3Style extends HeadingStyle {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Heading3",
|
id: "Heading3",
|
||||||
name: "Heading 3",
|
name: "Heading 3",
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,9 +59,9 @@ export class Heading3Style extends HeadingStyle {
|
|||||||
export class Heading4Style extends HeadingStyle {
|
export class Heading4Style extends HeadingStyle {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Heading4",
|
id: "Heading4",
|
||||||
name: "Heading 4",
|
name: "Heading 4",
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,9 +69,9 @@ export class Heading4Style extends HeadingStyle {
|
|||||||
export class Heading5Style extends HeadingStyle {
|
export class Heading5Style extends HeadingStyle {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Heading5",
|
id: "Heading5",
|
||||||
name: "Heading 5",
|
name: "Heading 5",
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,9 +79,9 @@ export class Heading5Style extends HeadingStyle {
|
|||||||
export class Heading6Style extends HeadingStyle {
|
export class Heading6Style extends HeadingStyle {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Heading6",
|
id: "Heading6",
|
||||||
name: "Heading 6",
|
name: "Heading 6",
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,9 +89,9 @@ export class Heading6Style extends HeadingStyle {
|
|||||||
export class StrongStyle extends HeadingStyle {
|
export class StrongStyle extends HeadingStyle {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Strong",
|
id: "Strong",
|
||||||
name: "Strong",
|
name: "Strong",
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,11 +99,11 @@ export class StrongStyle extends HeadingStyle {
|
|||||||
export class ListParagraph extends StyleForParagraph {
|
export class ListParagraph extends StyleForParagraph {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "ListParagraph",
|
id: "ListParagraph",
|
||||||
name: "List Paragraph",
|
name: "List Paragraph",
|
||||||
basedOn: "Normal",
|
basedOn: "Normal",
|
||||||
quickFormat: true,
|
quickFormat: true,
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,7 +111,6 @@ export class ListParagraph extends StyleForParagraph {
|
|||||||
export class FootnoteText extends StyleForParagraph {
|
export class FootnoteText extends StyleForParagraph {
|
||||||
public constructor(options: IBaseParagraphStyleOptions) {
|
public constructor(options: IBaseParagraphStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "FootnoteText",
|
id: "FootnoteText",
|
||||||
name: "footnote text",
|
name: "footnote text",
|
||||||
link: "FootnoteTextChar",
|
link: "FootnoteTextChar",
|
||||||
@ -129,6 +128,7 @@ export class FootnoteText extends StyleForParagraph {
|
|||||||
run: {
|
run: {
|
||||||
size: 20,
|
size: 20,
|
||||||
},
|
},
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +136,6 @@ export class FootnoteText extends StyleForParagraph {
|
|||||||
export class FootnoteReferenceStyle extends StyleForCharacter {
|
export class FootnoteReferenceStyle extends StyleForCharacter {
|
||||||
public constructor(options: IBaseCharacterStyleOptions) {
|
public constructor(options: IBaseCharacterStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "FootnoteReference",
|
id: "FootnoteReference",
|
||||||
name: "footnote reference",
|
name: "footnote reference",
|
||||||
basedOn: "DefaultParagraphFont",
|
basedOn: "DefaultParagraphFont",
|
||||||
@ -144,6 +143,7 @@ export class FootnoteReferenceStyle extends StyleForCharacter {
|
|||||||
run: {
|
run: {
|
||||||
superScript: true,
|
superScript: true,
|
||||||
},
|
},
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +151,6 @@ export class FootnoteReferenceStyle extends StyleForCharacter {
|
|||||||
export class FootnoteTextChar extends StyleForCharacter {
|
export class FootnoteTextChar extends StyleForCharacter {
|
||||||
public constructor(options: IBaseCharacterStyleOptions) {
|
public constructor(options: IBaseCharacterStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "FootnoteTextChar",
|
id: "FootnoteTextChar",
|
||||||
name: "Footnote Text Char",
|
name: "Footnote Text Char",
|
||||||
basedOn: "DefaultParagraphFont",
|
basedOn: "DefaultParagraphFont",
|
||||||
@ -160,6 +159,7 @@ export class FootnoteTextChar extends StyleForCharacter {
|
|||||||
run: {
|
run: {
|
||||||
size: 20,
|
size: 20,
|
||||||
},
|
},
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,7 +167,6 @@ export class FootnoteTextChar extends StyleForCharacter {
|
|||||||
export class HyperlinkStyle extends StyleForCharacter {
|
export class HyperlinkStyle extends StyleForCharacter {
|
||||||
public constructor(options: IBaseCharacterStyleOptions) {
|
public constructor(options: IBaseCharacterStyleOptions) {
|
||||||
super({
|
super({
|
||||||
...options,
|
|
||||||
id: "Hyperlink",
|
id: "Hyperlink",
|
||||||
name: "Hyperlink",
|
name: "Hyperlink",
|
||||||
basedOn: "DefaultParagraphFont",
|
basedOn: "DefaultParagraphFont",
|
||||||
@ -177,6 +176,7 @@ export class HyperlinkStyle extends StyleForCharacter {
|
|||||||
type: UnderlineType.SINGLE,
|
type: UnderlineType.SINGLE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
import { StringValueElement, XmlComponent } from "@file/xml-components";
|
import { StringValueElement, XmlComponent } from "@file/xml-components";
|
||||||
|
|
||||||
export class StructuredDocumentTagProperties extends XmlComponent {
|
export class StructuredDocumentTagProperties extends XmlComponent {
|
||||||
public constructor(alias: string) {
|
public constructor(alias?: string) {
|
||||||
super("w:sdtPr");
|
super("w:sdtPr");
|
||||||
|
|
||||||
|
if (alias) {
|
||||||
this.root.push(new StringValueElement("w:alias", alias));
|
this.root.push(new StringValueElement("w:alias", alias));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ describe("ImportedXmlComponent", () => {
|
|||||||
otherAttr: "2",
|
otherAttr: "2",
|
||||||
};
|
};
|
||||||
importedXmlComponent = new ImportedXmlComponent("w:test", attributes);
|
importedXmlComponent = new ImportedXmlComponent("w:test", attributes);
|
||||||
|
// eslint-disable-next-line functional/immutable-data
|
||||||
importedXmlComponent.push(new ImportedXmlComponent("w:child"));
|
importedXmlComponent.push(new ImportedXmlComponent("w:child"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ export const convertToXmlComponent = (element: XmlElement): ImportedXmlComponent
|
|||||||
for (const childElm of childElements) {
|
for (const childElm of childElements) {
|
||||||
const child = convertToXmlComponent(childElm);
|
const child = convertToXmlComponent(childElm);
|
||||||
if (child !== undefined) {
|
if (child !== undefined) {
|
||||||
|
// eslint-disable-next-line functional/immutable-data
|
||||||
xmlComponent.push(child);
|
xmlComponent.push(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +61,7 @@ export class ImportedXmlComponent extends XmlComponent {
|
|||||||
public constructor(rootKey: string, _attr?: any) {
|
public constructor(rootKey: string, _attr?: any) {
|
||||||
super(rootKey);
|
super(rootKey);
|
||||||
if (_attr) {
|
if (_attr) {
|
||||||
|
// eslint-disable-next-line functional/immutable-data
|
||||||
this.root.push(new ImportedXmlComponentAttributes(_attr));
|
this.root.push(new ImportedXmlComponentAttributes(_attr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,13 @@ describe("XmlComponent", () => {
|
|||||||
});
|
});
|
||||||
it("should handle children elements", () => {
|
it("should handle children elements", () => {
|
||||||
const xmlComponent = new TestComponent("w:test");
|
const xmlComponent = new TestComponent("w:test");
|
||||||
|
// eslint-disable-next-line functional/immutable-data
|
||||||
xmlComponent.push(
|
xmlComponent.push(
|
||||||
new Attributes({
|
new Attributes({
|
||||||
val: "test",
|
val: "test",
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
// eslint-disable-next-line functional/immutable-data
|
||||||
xmlComponent.push(new TestComponent("innerTest"));
|
xmlComponent.push(new TestComponent("innerTest"));
|
||||||
|
|
||||||
const tree = new Formatter().format(xmlComponent);
|
const tree = new Formatter().format(xmlComponent);
|
||||||
@ -43,6 +45,7 @@ describe("XmlComponent", () => {
|
|||||||
});
|
});
|
||||||
it("should hoist attrs if only attrs are present", () => {
|
it("should hoist attrs if only attrs are present", () => {
|
||||||
const xmlComponent = new TestComponent("w:test");
|
const xmlComponent = new TestComponent("w:test");
|
||||||
|
// eslint-disable-next-line functional/immutable-data
|
||||||
xmlComponent.push(
|
xmlComponent.push(
|
||||||
new Attributes({
|
new Attributes({
|
||||||
val: "test",
|
val: "test",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export interface IXmlAttribute {
|
export interface IXmlAttribute {
|
||||||
readonly [key: string]: string | number | boolean;
|
readonly [key: string]: string | number | boolean;
|
||||||
}
|
}
|
||||||
export interface IXmlableObject extends Object {
|
export interface IXmlableObject extends Record<string, unknown> {
|
||||||
// readonly _attr?: IXmlAttribute;
|
// readonly _attr?: IXmlAttribute;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
readonly [key: string]: any;
|
readonly [key: string]: any;
|
||||||
|
@ -47,5 +47,31 @@ describe("content-types-manager", () => {
|
|||||||
type: "element",
|
type: "element",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not append duplicate content type", () => {
|
||||||
|
const element = {
|
||||||
|
type: "element",
|
||||||
|
name: "xml",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "Types",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "Default",
|
||||||
|
attributes: {
|
||||||
|
ContentType: "image/png",
|
||||||
|
Extension: "png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
appendContentType(element, "image/png", "png");
|
||||||
|
|
||||||
|
expect(element.elements.length).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,18 @@ import { getFirstLevelElements } from "./util";
|
|||||||
|
|
||||||
export const appendContentType = (element: Element, contentType: string, extension: string): void => {
|
export const appendContentType = (element: Element, contentType: string, extension: string): void => {
|
||||||
const relationshipElements = getFirstLevelElements(element, "Types");
|
const relationshipElements = getFirstLevelElements(element, "Types");
|
||||||
|
|
||||||
|
const exist = relationshipElements.some(
|
||||||
|
(el) =>
|
||||||
|
el.type === "element" &&
|
||||||
|
el.name === "Default" &&
|
||||||
|
el?.attributes?.ContentType === contentType &&
|
||||||
|
el?.attributes?.Extension === extension,
|
||||||
|
);
|
||||||
|
if (exist) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line functional/immutable-data
|
// eslint-disable-next-line functional/immutable-data
|
||||||
relationshipElements.push({
|
relationshipElements.push({
|
||||||
attributes: {
|
attributes: {
|
||||||
|
@ -49,11 +49,12 @@ export type IPatch = ParagraphPatch | FilePatch;
|
|||||||
|
|
||||||
export interface PatchDocumentOptions {
|
export interface PatchDocumentOptions {
|
||||||
readonly patches: { readonly [key: string]: IPatch };
|
readonly patches: { readonly [key: string]: IPatch };
|
||||||
|
readonly keepOriginalStyles?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageReplacer = new ImageReplacer();
|
const imageReplacer = new ImageReplacer();
|
||||||
|
|
||||||
export const patchDocument = async (data: InputDataType, options: PatchDocumentOptions): Promise<Buffer> => {
|
export const patchDocument = async (data: InputDataType, options: PatchDocumentOptions): Promise<Uint8Array> => {
|
||||||
const zipContent = await JSZip.loadAsync(data);
|
const zipContent = await JSZip.loadAsync(data);
|
||||||
const contexts = new Map<string, IContext>();
|
const contexts = new Map<string, IContext>();
|
||||||
const file = {
|
const file = {
|
||||||
@ -68,11 +69,11 @@ export const patchDocument = async (data: InputDataType, options: PatchDocumentO
|
|||||||
const hyperlinkRelationshipAdditions: IHyperlinkRelationshipAddition[] = [];
|
const hyperlinkRelationshipAdditions: IHyperlinkRelationshipAddition[] = [];
|
||||||
let hasMedia = false;
|
let hasMedia = false;
|
||||||
|
|
||||||
const binaryContentMap = new Map<string, Buffer>();
|
const binaryContentMap = new Map<string, Uint8Array>();
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(zipContent.files)) {
|
for (const [key, value] of Object.entries(zipContent.files)) {
|
||||||
if (!key.endsWith(".xml") && !key.endsWith(".rels")) {
|
if (!key.endsWith(".xml") && !key.endsWith(".rels")) {
|
||||||
binaryContentMap.set(key, await value.async("nodebuffer"));
|
binaryContentMap.set(key, await value.async("uint8array"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +129,7 @@ export const patchDocument = async (data: InputDataType, options: PatchDocumentO
|
|||||||
patchText,
|
patchText,
|
||||||
renderedParagraphs,
|
renderedParagraphs,
|
||||||
context,
|
context,
|
||||||
|
options.keepOriginalStyles,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +215,7 @@ export const patchDocument = async (data: InputDataType, options: PatchDocumentO
|
|||||||
}
|
}
|
||||||
|
|
||||||
return zip.generateAsync({
|
return zip.generateAsync({
|
||||||
type: "nodebuffer",
|
type: "uint8array",
|
||||||
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
compression: "DEFLATE",
|
compression: "DEFLATE",
|
||||||
});
|
});
|
||||||
|
@ -43,6 +43,7 @@ export const replaceTokenInParagraphElement = ({
|
|||||||
patchTextElement(paragraphElement.elements![run.index].elements![index], firstPart);
|
patchTextElement(paragraphElement.elements![run.index].elements![index], firstPart);
|
||||||
replaceMode = ReplaceMode.MIDDLE;
|
replaceMode = ReplaceMode.MIDDLE;
|
||||||
continue;
|
continue;
|
||||||
|
/* c8 ignore next 2 */
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ReplaceMode.MIDDLE:
|
case ReplaceMode.MIDDLE:
|
||||||
@ -59,6 +60,7 @@ export const replaceTokenInParagraphElement = ({
|
|||||||
patchTextElement(paragraphElement.elements![run.index].elements![index], "");
|
patchTextElement(paragraphElement.elements![run.index].elements![index], "");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
/* c8 ignore next */
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,28 @@ const MOCK_JSON = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:p",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [{ type: "element", name: "w:b", attributes: { "w:val": "1" } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "What a {{bold}} text!" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -115,6 +137,93 @@ describe("replacer", () => {
|
|||||||
expect(JSON.stringify(output)).to.contain("Delightful Header");
|
expect(JSON.stringify(output)).to.contain("Delightful Header");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should replace paragraph type keeping original styling if keepOriginalStyles is true", () => {
|
||||||
|
const output = replacer(
|
||||||
|
MOCK_JSON,
|
||||||
|
{
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [new TextRun("sweet")],
|
||||||
|
},
|
||||||
|
"{{bold}}",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: "What a {{bold}} text!",
|
||||||
|
runs: [
|
||||||
|
{
|
||||||
|
text: "What a {{bold}} text!",
|
||||||
|
parts: [{ text: "What a {{bold}} text!", index: 1, start: 0, end: 21 }],
|
||||||
|
index: 0,
|
||||||
|
start: 0,
|
||||||
|
end: 21,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
index: 0,
|
||||||
|
path: [0, 0, 1],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
file: {} as unknown as File,
|
||||||
|
viewWrapper: {
|
||||||
|
Relationships: {},
|
||||||
|
} as unknown as IViewWrapper,
|
||||||
|
stack: [],
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(JSON.stringify(output)).to.contain("sweet");
|
||||||
|
expect(output.elements![0].elements![1].elements).toMatchObject([
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [{ type: "element", name: "w:b", attributes: { "w:val": "1" } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "What a " }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [{ type: "element", name: "w:b", attributes: { "w:val": "1" } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: "sweet" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:r",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:rPr",
|
||||||
|
elements: [{ type: "element", name: "w:b", attributes: { "w:val": "1" } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "w:t",
|
||||||
|
elements: [{ type: "text", text: " text!" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should replace document type", () => {
|
it("should replace document type", () => {
|
||||||
const output = replacer(
|
const output = replacer(
|
||||||
MOCK_JSON,
|
MOCK_JSON,
|
||||||
|
@ -20,6 +20,7 @@ export const replacer = (
|
|||||||
patchText: string,
|
patchText: string,
|
||||||
renderedParagraphs: readonly IRenderedParagraphNode[],
|
renderedParagraphs: readonly IRenderedParagraphNode[],
|
||||||
context: IContext,
|
context: IContext,
|
||||||
|
keepOriginalStyles: boolean = false,
|
||||||
): Element => {
|
): Element => {
|
||||||
for (const renderedParagraph of renderedParagraphs) {
|
for (const renderedParagraph of renderedParagraphs) {
|
||||||
const textJson = patch.children
|
const textJson = patch.children
|
||||||
@ -47,9 +48,30 @@ export const replacer = (
|
|||||||
|
|
||||||
const index = findRunElementIndexWithToken(paragraphElement, SPLIT_TOKEN);
|
const index = findRunElementIndexWithToken(paragraphElement, SPLIT_TOKEN);
|
||||||
|
|
||||||
const { left, right } = splitRunElement(paragraphElement.elements![index], SPLIT_TOKEN);
|
const runElementToBeReplaced = paragraphElement.elements![index];
|
||||||
|
const { left, right } = splitRunElement(runElementToBeReplaced, SPLIT_TOKEN);
|
||||||
|
|
||||||
|
let newRunElements = textJson;
|
||||||
|
let patchedRightElement = right;
|
||||||
|
|
||||||
|
if (keepOriginalStyles) {
|
||||||
|
const runElementNonTextualElements = runElementToBeReplaced.elements!.filter(
|
||||||
|
(e) => e.type === "element" && e.name !== "w:t",
|
||||||
|
);
|
||||||
|
|
||||||
|
newRunElements = textJson.map((e) => ({
|
||||||
|
...e,
|
||||||
|
elements: [...runElementNonTextualElements, ...e.elements!],
|
||||||
|
}));
|
||||||
|
|
||||||
|
patchedRightElement = {
|
||||||
|
...right,
|
||||||
|
elements: [...runElementNonTextualElements, ...right.elements!],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line functional/immutable-data
|
// eslint-disable-next-line functional/immutable-data
|
||||||
paragraphElement.elements!.splice(index, 1, left, ...textJson, right);
|
paragraphElement.elements!.splice(index, 1, left, ...newRunElements, patchedRightElement);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,10 @@ export default defineConfig({
|
|||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
dts(),
|
dts(),
|
||||||
nodePolyfills({
|
nodePolyfills({
|
||||||
exclude: ["fs"],
|
exclude: [],
|
||||||
|
globals: {
|
||||||
|
Buffer: false,
|
||||||
|
},
|
||||||
protocolImports: true,
|
protocolImports: true,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@ -26,7 +29,25 @@ export default defineConfig({
|
|||||||
lib: {
|
lib: {
|
||||||
entry: [resolve(__dirname, "src/index.ts")],
|
entry: [resolve(__dirname, "src/index.ts")],
|
||||||
name: "docx",
|
name: "docx",
|
||||||
fileName: "index",
|
fileName: (d) => {
|
||||||
|
if (d === "umd") {
|
||||||
|
return "index.umd.js";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d === "cjs") {
|
||||||
|
return "index.cjs";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d === "es") {
|
||||||
|
return "index.mjs";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d === "iife") {
|
||||||
|
return "index.iife.js";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown";
|
||||||
|
},
|
||||||
formats: ["iife", "es", "cjs", "umd"],
|
formats: ["iife", "es", "cjs", "umd"],
|
||||||
},
|
},
|
||||||
outDir: resolve(__dirname, "build"),
|
outDir: resolve(__dirname, "build"),
|
||||||
|
Reference in New Issue
Block a user