@zipify/wysiwyg 1.0.0-dev.97 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/wysiwyg.css +7 -0
- package/dist/wysiwyg.mjs +96 -30
- package/lib/enums/TextSettings.js +3 -1
- package/lib/extensions/Margin.js +38 -0
- package/lib/extensions/__tests__/Margin.test.js +59 -0
- package/lib/extensions/__tests__/__snapshots__/Margin.test.js.snap +69 -0
- package/lib/extensions/core/plugins/PastePlugin.js +22 -1
- package/lib/extensions/index.js +9 -6
- package/lib/services/ContentNormalizer.js +45 -24
- package/lib/services/__tests__/ContentNormalizer.test.js +0 -7
- package/lib/styles/content.css +8 -0
- package/package.json +1 -1
package/dist/wysiwyg.css
CHANGED
|
@@ -754,6 +754,13 @@ $font-height--md: 1.72;
|
|
|
754
754
|
font-style: var(--zw-font-style, var(--zw-preset-font-style));
|
|
755
755
|
text-decoration: var(--zw-text-decoration, var(--zw-preset-text-decoration));
|
|
756
756
|
background-color: var(--zw-background-color, var(--zw-preset-background-color));
|
|
757
|
+
margin: var(--zw-margin);
|
|
758
|
+
}
|
|
759
|
+
h1.zw-style.zw-style.zw-style,
|
|
760
|
+
h2.zw-style.zw-style.zw-style,
|
|
761
|
+
h3.zw-style.zw-style.zw-style,
|
|
762
|
+
h4.zw-style.zw-style.zw-style {
|
|
763
|
+
margin: var(--zw-margin, 8px 0);
|
|
757
764
|
}
|
|
758
765
|
@media (min-width: 1200px) {
|
|
759
766
|
.zw-style.zw-style.zw-style {
|
package/dist/wysiwyg.mjs
CHANGED
|
@@ -13971,10 +13971,12 @@ const TextSettings = Object.freeze({
|
|
|
13971
13971
|
LINE_HEIGHT: "line_height",
|
|
13972
13972
|
TEXT_DECORATION: "text_decoration",
|
|
13973
13973
|
SUPERSCRIPT: "superscript",
|
|
13974
|
+
MARGIN: "margin",
|
|
13974
13975
|
get attributes() {
|
|
13975
13976
|
return [
|
|
13976
13977
|
this.ALIGNMENT,
|
|
13977
|
-
this.LINE_HEIGHT
|
|
13978
|
+
this.LINE_HEIGHT,
|
|
13979
|
+
this.MARGIN
|
|
13978
13980
|
];
|
|
13979
13981
|
},
|
|
13980
13982
|
get marks() {
|
|
@@ -19438,42 +19440,54 @@ class FavoriteColors {
|
|
|
19438
19440
|
}
|
|
19439
19441
|
}
|
|
19440
19442
|
const _ContentNormalizer = class {
|
|
19443
|
+
static build(content) {
|
|
19444
|
+
return new _ContentNormalizer({
|
|
19445
|
+
parser: _ContentNormalizer.PARSER,
|
|
19446
|
+
content
|
|
19447
|
+
});
|
|
19448
|
+
}
|
|
19441
19449
|
static normalize(content) {
|
|
19442
|
-
|
|
19443
|
-
return new _ContentNormalizer(options).normalize();
|
|
19450
|
+
return _ContentNormalizer.build(content).normalize();
|
|
19444
19451
|
}
|
|
19445
19452
|
constructor({ content, parser }) {
|
|
19446
19453
|
this._content = content;
|
|
19447
19454
|
this._parser = parser;
|
|
19448
|
-
this.
|
|
19455
|
+
this.dom = null;
|
|
19449
19456
|
}
|
|
19450
19457
|
normalize() {
|
|
19451
19458
|
if (typeof this._content !== "string") {
|
|
19452
19459
|
return this._content;
|
|
19453
19460
|
}
|
|
19454
|
-
|
|
19461
|
+
this.normalizeHTML();
|
|
19462
|
+
return this.normalizedHTML;
|
|
19455
19463
|
}
|
|
19456
|
-
|
|
19457
|
-
this.
|
|
19464
|
+
normalizeHTML() {
|
|
19465
|
+
this.dom = this._parser.parseFromString(this._content.replace(/(\r)?\n/g, ""), "text/html");
|
|
19458
19466
|
this._removeComments();
|
|
19459
|
-
this.
|
|
19460
|
-
this.
|
|
19461
|
-
this.
|
|
19462
|
-
this.
|
|
19463
|
-
this.
|
|
19464
|
-
|
|
19467
|
+
this.iterateNodes(this._normalizeBreakLines, (node) => node.tagName === "BR");
|
|
19468
|
+
this.iterateNodes(this._removeEmptyNodes, this._isBlockNode);
|
|
19469
|
+
this.iterateNodes(this._normalizeListItems, (node) => node.tagName === "LI");
|
|
19470
|
+
this.iterateNodes(this._normalizeSettingsStructure, (node) => node.tagName === "SPAN");
|
|
19471
|
+
this.iterateNodes(this._normalizeStyles, (node) => node.tagName !== "SPAN");
|
|
19472
|
+
}
|
|
19473
|
+
get normalizedHTML() {
|
|
19474
|
+
return this.dom.body.innerHTML;
|
|
19465
19475
|
}
|
|
19466
19476
|
_removeComments() {
|
|
19467
|
-
const iterator = this.
|
|
19468
|
-
this.
|
|
19477
|
+
const iterator = this.createNodeIterator(NodeFilter.SHOW_COMMENT);
|
|
19478
|
+
this.runIterator(iterator, (node) => node.remove());
|
|
19479
|
+
}
|
|
19480
|
+
createNodeIterator(whatToShow, filter2) {
|
|
19481
|
+
return this.dom.createNodeIterator(this.dom.body, whatToShow, filter2);
|
|
19469
19482
|
}
|
|
19470
|
-
|
|
19471
|
-
const
|
|
19472
|
-
|
|
19483
|
+
iterateNodes(handler, condition = () => true) {
|
|
19484
|
+
const checkCondition = (node) => node.tagName !== "BODY" && condition.call(this, node);
|
|
19485
|
+
const iterator = this.createNodeIterator(NodeFilter.SHOW_ELEMENT, {
|
|
19486
|
+
acceptNode: (node) => checkCondition(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
|
|
19473
19487
|
});
|
|
19474
|
-
this.
|
|
19488
|
+
this.runIterator(iterator, handler);
|
|
19475
19489
|
}
|
|
19476
|
-
|
|
19490
|
+
runIterator(iterator, handler) {
|
|
19477
19491
|
let currentNode = iterator.nextNode();
|
|
19478
19492
|
while (currentNode) {
|
|
19479
19493
|
handler.call(this, currentNode);
|
|
@@ -19481,8 +19495,7 @@ const _ContentNormalizer = class {
|
|
|
19481
19495
|
}
|
|
19482
19496
|
}
|
|
19483
19497
|
_removeEmptyNodes(node) {
|
|
19484
|
-
|
|
19485
|
-
if (!html2)
|
|
19498
|
+
if (!node.innerHTML.trim())
|
|
19486
19499
|
node.remove();
|
|
19487
19500
|
}
|
|
19488
19501
|
_normalizeListItems(itemEl) {
|
|
@@ -19617,8 +19630,16 @@ const _ContentNormalizer = class {
|
|
|
19617
19630
|
};
|
|
19618
19631
|
let ContentNormalizer = _ContentNormalizer;
|
|
19619
19632
|
__publicField(ContentNormalizer, "PARSER", new DOMParser());
|
|
19620
|
-
__publicField(ContentNormalizer, "BLOCK_STYLES", ["text-align", "line-height"]);
|
|
19621
19633
|
__publicField(ContentNormalizer, "BLOCK_NODE_NAMES", ["P", "H1", "H2", "H3", "H4"]);
|
|
19634
|
+
__publicField(ContentNormalizer, "BLOCK_STYLES", [
|
|
19635
|
+
"text-align",
|
|
19636
|
+
"line-height",
|
|
19637
|
+
"margin",
|
|
19638
|
+
"margin-top",
|
|
19639
|
+
"margin-bottom",
|
|
19640
|
+
"margin-left",
|
|
19641
|
+
"margin-right"
|
|
19642
|
+
]);
|
|
19622
19643
|
__publicField(ContentNormalizer, "ASSIGN_STYLE_RULES", [
|
|
19623
19644
|
{
|
|
19624
19645
|
tag: /^(b|strong)$/,
|
|
@@ -25690,10 +25711,26 @@ class ProseMirrorPlugin {
|
|
|
25690
25711
|
class PastePlugin extends ProseMirrorPlugin {
|
|
25691
25712
|
buildProps() {
|
|
25692
25713
|
return {
|
|
25693
|
-
transformPastedHTML:
|
|
25714
|
+
transformPastedHTML: this._transformPastedHTML.bind(this),
|
|
25694
25715
|
handlePaste: this._handlePaste.bind(this)
|
|
25695
25716
|
};
|
|
25696
25717
|
}
|
|
25718
|
+
_transformPastedHTML(html2) {
|
|
25719
|
+
const normalizer = ContentNormalizer.build(html2);
|
|
25720
|
+
normalizer.normalizeHTML();
|
|
25721
|
+
this._removeDeprecatedStyles(normalizer);
|
|
25722
|
+
return normalizer.normalizedHTML;
|
|
25723
|
+
}
|
|
25724
|
+
_removeDeprecatedStyles(normalizer) {
|
|
25725
|
+
const elements = normalizer.dom.querySelectorAll('[style*="margin"]');
|
|
25726
|
+
for (const element of Array.from(elements)) {
|
|
25727
|
+
element.style.removeProperty("margin");
|
|
25728
|
+
element.style.removeProperty("margin-top");
|
|
25729
|
+
element.style.removeProperty("margin-right");
|
|
25730
|
+
element.style.removeProperty("margin-bottom");
|
|
25731
|
+
element.style.removeProperty("margin-left");
|
|
25732
|
+
}
|
|
25733
|
+
}
|
|
25697
25734
|
_handlePaste(view, _, slice2) {
|
|
25698
25735
|
const transaction = this._insertPastedContent(view, slice2).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste");
|
|
25699
25736
|
view.dispatch(transaction);
|
|
@@ -25748,6 +25785,34 @@ const buildCoreExtensions = () => [
|
|
|
25748
25785
|
SelectionProcessor,
|
|
25749
25786
|
CopyPasteProcessor
|
|
25750
25787
|
];
|
|
25788
|
+
const Margin = Extension.create({
|
|
25789
|
+
name: TextSettings.MARGIN,
|
|
25790
|
+
addGlobalAttributes: () => [
|
|
25791
|
+
{
|
|
25792
|
+
types: [NodeTypes.PARAGRAPH, NodeTypes.HEADING],
|
|
25793
|
+
attributes: {
|
|
25794
|
+
[TextSettings.MARGIN]: {
|
|
25795
|
+
isRequired: false,
|
|
25796
|
+
parseHTML(el) {
|
|
25797
|
+
const { margin, marginTop, marginRight, marginBottom, marginLeft } = el.style;
|
|
25798
|
+
const isPreset = [margin, marginTop, marginRight, marginBottom, marginLeft].some((v) => !!v);
|
|
25799
|
+
if (!isPreset)
|
|
25800
|
+
return null;
|
|
25801
|
+
if (margin)
|
|
25802
|
+
return { value: margin };
|
|
25803
|
+
const value = [marginTop || 0, marginRight || 0, marginBottom || 0, marginLeft || 0].join(" ");
|
|
25804
|
+
return { value };
|
|
25805
|
+
},
|
|
25806
|
+
renderHTML(attrs) {
|
|
25807
|
+
if (!attrs.margin)
|
|
25808
|
+
return null;
|
|
25809
|
+
return renderInlineSetting({ margin: attrs.margin.value });
|
|
25810
|
+
}
|
|
25811
|
+
}
|
|
25812
|
+
}
|
|
25813
|
+
}
|
|
25814
|
+
]
|
|
25815
|
+
});
|
|
25751
25816
|
function buildExtensions(options) {
|
|
25752
25817
|
const getPresetById = (id2) => options.presetsRef.value.find((preset) => preset.id === id2);
|
|
25753
25818
|
const defaultPreset = getPresetById(options.defaultPresetId);
|
|
@@ -25765,16 +25830,16 @@ function buildExtensions(options) {
|
|
|
25765
25830
|
DeviceManager.configure({
|
|
25766
25831
|
device: options.deviceRef
|
|
25767
25832
|
}),
|
|
25768
|
-
FontFamily.configure({
|
|
25769
|
-
fonts: options.fonts,
|
|
25770
|
-
defaultFont: defaultPreset.common.font_family
|
|
25771
|
-
}),
|
|
25772
|
-
FontWeight,
|
|
25773
25833
|
FontSize.configure({
|
|
25774
25834
|
minSize: options.minFontSize,
|
|
25775
25835
|
maxSize: options.maxFontSize,
|
|
25776
25836
|
wrapperRef: options.wrapperRef
|
|
25777
25837
|
}),
|
|
25838
|
+
FontFamily.configure({
|
|
25839
|
+
fonts: options.fonts,
|
|
25840
|
+
defaultFont: defaultPreset.common.font_family
|
|
25841
|
+
}),
|
|
25842
|
+
FontWeight,
|
|
25778
25843
|
FontColor,
|
|
25779
25844
|
BackgroundColor,
|
|
25780
25845
|
FontStyle,
|
|
@@ -25789,7 +25854,8 @@ function buildExtensions(options) {
|
|
|
25789
25854
|
preset: linkPreset,
|
|
25790
25855
|
basePresetClass: options.basePresetClass,
|
|
25791
25856
|
pageBlocks: options.pageBlocksRef
|
|
25792
|
-
})
|
|
25857
|
+
}),
|
|
25858
|
+
Margin
|
|
25793
25859
|
]);
|
|
25794
25860
|
}
|
|
25795
25861
|
class Font {
|
|
@@ -9,11 +9,13 @@ export const TextSettings = Object.freeze({
|
|
|
9
9
|
LINE_HEIGHT: 'line_height',
|
|
10
10
|
TEXT_DECORATION: 'text_decoration',
|
|
11
11
|
SUPERSCRIPT: 'superscript',
|
|
12
|
+
MARGIN: 'margin',
|
|
12
13
|
|
|
13
14
|
get attributes() {
|
|
14
15
|
return [
|
|
15
16
|
this.ALIGNMENT,
|
|
16
|
-
this.LINE_HEIGHT
|
|
17
|
+
this.LINE_HEIGHT,
|
|
18
|
+
this.MARGIN
|
|
17
19
|
];
|
|
18
20
|
},
|
|
19
21
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/vue-2';
|
|
2
|
+
import { NodeTypes, TextSettings } from '../enums';
|
|
3
|
+
import { renderInlineSetting } from '../utils';
|
|
4
|
+
|
|
5
|
+
// Fallback margins in old wysiwyg content
|
|
6
|
+
export const Margin = Extension.create({
|
|
7
|
+
name: TextSettings.MARGIN,
|
|
8
|
+
|
|
9
|
+
addGlobalAttributes: () => [
|
|
10
|
+
{
|
|
11
|
+
types: [NodeTypes.PARAGRAPH, NodeTypes.HEADING],
|
|
12
|
+
|
|
13
|
+
attributes: {
|
|
14
|
+
[TextSettings.MARGIN]: {
|
|
15
|
+
isRequired: false,
|
|
16
|
+
|
|
17
|
+
parseHTML(el) {
|
|
18
|
+
const { margin, marginTop, marginRight, marginBottom, marginLeft } = el.style;
|
|
19
|
+
const isPreset = [margin, marginTop, marginRight, marginBottom, marginLeft].some((v) => !!v);
|
|
20
|
+
|
|
21
|
+
if (!isPreset) return null;
|
|
22
|
+
if (margin) return { value: margin };
|
|
23
|
+
|
|
24
|
+
const value = [marginTop || 0, marginRight || 0, marginBottom || 0, marginLeft || 0].join(' ');
|
|
25
|
+
|
|
26
|
+
return { value };
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
renderHTML(attrs) {
|
|
30
|
+
if (!attrs.margin) return null;
|
|
31
|
+
|
|
32
|
+
return renderInlineSetting({ margin: attrs.margin.value });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core';
|
|
2
|
+
import { ContentNormalizer, NodeFactory } from '../../services';
|
|
3
|
+
import { buildCoreExtensions } from '../core';
|
|
4
|
+
import { Margin } from '../Margin';
|
|
5
|
+
|
|
6
|
+
function createEditor({ content }) {
|
|
7
|
+
return new Editor({
|
|
8
|
+
content: ContentNormalizer.normalize(content),
|
|
9
|
+
extensions: buildCoreExtensions().concat(Margin)
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('parse html', () => {
|
|
14
|
+
test('should set null if no margin', () => {
|
|
15
|
+
const editor = createEditor({
|
|
16
|
+
content: '<p>lorem ipsum</p>'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(editor.getJSON()).toMatchSnapshot();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should parse shorthand', () => {
|
|
23
|
+
const editor = createEditor({
|
|
24
|
+
content: '<p style="margin: 10px 3px 1em;">lorem ipsum</p>'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(editor.getJSON()).toMatchSnapshot();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should parse values', () => {
|
|
31
|
+
const editor = createEditor({
|
|
32
|
+
content: '<p style="margin-left: 10px; margin-bottom: 1em">lorem ipsum</p>'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(editor.getJSON()).toMatchSnapshot();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('render html', () => {
|
|
40
|
+
test('should not render empty', () => {
|
|
41
|
+
const editor = createEditor({
|
|
42
|
+
content: NodeFactory.doc([
|
|
43
|
+
NodeFactory.paragraph({ margin: null }, 'lorem ipsum')
|
|
44
|
+
])
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(editor.getHTML()).toMatchSnapshot();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should render value', () => {
|
|
51
|
+
const editor = createEditor({
|
|
52
|
+
content: NodeFactory.doc([
|
|
53
|
+
NodeFactory.paragraph({ margin: { value: '10px' } }, 'lorem ipsum')
|
|
54
|
+
])
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(editor.getHTML()).toMatchSnapshot();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`parse html should parse shorthand 1`] = `
|
|
4
|
+
Object {
|
|
5
|
+
"content": Array [
|
|
6
|
+
Object {
|
|
7
|
+
"attrs": Object {
|
|
8
|
+
"margin": Object {
|
|
9
|
+
"value": "10px 3px 1em",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
"content": Array [
|
|
13
|
+
Object {
|
|
14
|
+
"text": "lorem ipsum",
|
|
15
|
+
"type": "text",
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
"type": "paragraph",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
"type": "doc",
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
exports[`parse html should parse values 1`] = `
|
|
26
|
+
Object {
|
|
27
|
+
"content": Array [
|
|
28
|
+
Object {
|
|
29
|
+
"attrs": Object {
|
|
30
|
+
"margin": Object {
|
|
31
|
+
"value": "0 0 1em 10px",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"content": Array [
|
|
35
|
+
Object {
|
|
36
|
+
"text": "lorem ipsum",
|
|
37
|
+
"type": "text",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
"type": "paragraph",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
"type": "doc",
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
exports[`parse html should set null if no margin 1`] = `
|
|
48
|
+
Object {
|
|
49
|
+
"content": Array [
|
|
50
|
+
Object {
|
|
51
|
+
"attrs": Object {
|
|
52
|
+
"margin": null,
|
|
53
|
+
},
|
|
54
|
+
"content": Array [
|
|
55
|
+
Object {
|
|
56
|
+
"text": "lorem ipsum",
|
|
57
|
+
"type": "text",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
"type": "paragraph",
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
"type": "doc",
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
exports[`render html should not render empty 1`] = `"<p class=\\"zw-style\\">lorem ipsum</p>"`;
|
|
68
|
+
|
|
69
|
+
exports[`render html should render value 1`] = `"<p class=\\"zw-style\\" style=\\"--zw-margin:10px;\\">lorem ipsum</p>"`;
|
|
@@ -5,11 +5,32 @@ import { ProseMirrorPlugin } from './ProseMirrorPlugin';
|
|
|
5
5
|
export class PastePlugin extends ProseMirrorPlugin {
|
|
6
6
|
buildProps() {
|
|
7
7
|
return {
|
|
8
|
-
transformPastedHTML:
|
|
8
|
+
transformPastedHTML: this._transformPastedHTML.bind(this),
|
|
9
9
|
handlePaste: this._handlePaste.bind(this)
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
_transformPastedHTML(html) {
|
|
14
|
+
const normalizer = ContentNormalizer.build(html);
|
|
15
|
+
|
|
16
|
+
normalizer.normalizeHTML();
|
|
17
|
+
this._removeDeprecatedStyles(normalizer);
|
|
18
|
+
|
|
19
|
+
return normalizer.normalizedHTML;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_removeDeprecatedStyles(normalizer) {
|
|
23
|
+
const elements = normalizer.dom.querySelectorAll('[style*="margin"]');
|
|
24
|
+
|
|
25
|
+
for (const element of Array.from(elements)) {
|
|
26
|
+
element.style.removeProperty('margin');
|
|
27
|
+
element.style.removeProperty('margin-top');
|
|
28
|
+
element.style.removeProperty('margin-right');
|
|
29
|
+
element.style.removeProperty('margin-bottom');
|
|
30
|
+
element.style.removeProperty('margin-left');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
13
34
|
_handlePaste(view, _, slice) {
|
|
14
35
|
const transaction = this._insertPastedContent(view, slice)
|
|
15
36
|
.scrollIntoView()
|
package/lib/extensions/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { List } from './list';
|
|
|
14
14
|
import { Link } from './Link';
|
|
15
15
|
import { Superscript } from './Superscript';
|
|
16
16
|
import { buildCoreExtensions } from './core';
|
|
17
|
+
import { Margin } from './Margin';
|
|
17
18
|
|
|
18
19
|
export function buildExtensions(options) {
|
|
19
20
|
const getPresetById = (id) => options.presetsRef.value.find((preset) => preset.id === id);
|
|
@@ -33,16 +34,17 @@ export function buildExtensions(options) {
|
|
|
33
34
|
DeviceManager.configure({
|
|
34
35
|
device: options.deviceRef
|
|
35
36
|
}),
|
|
36
|
-
|
|
37
|
-
fonts: options.fonts,
|
|
38
|
-
defaultFont: defaultPreset.common.font_family
|
|
39
|
-
}),
|
|
40
|
-
FontWeight,
|
|
37
|
+
// Should be first setting. Fix size of wrappers in headings
|
|
41
38
|
FontSize.configure({
|
|
42
39
|
minSize: options.minFontSize,
|
|
43
40
|
maxSize: options.maxFontSize,
|
|
44
41
|
wrapperRef: options.wrapperRef
|
|
45
42
|
}),
|
|
43
|
+
FontFamily.configure({
|
|
44
|
+
fonts: options.fonts,
|
|
45
|
+
defaultFont: defaultPreset.common.font_family
|
|
46
|
+
}),
|
|
47
|
+
FontWeight,
|
|
46
48
|
FontColor,
|
|
47
49
|
BackgroundColor,
|
|
48
50
|
FontStyle,
|
|
@@ -57,6 +59,7 @@ export function buildExtensions(options) {
|
|
|
57
59
|
preset: linkPreset,
|
|
58
60
|
basePresetClass: options.basePresetClass,
|
|
59
61
|
pageBlocks: options.pageBlocksRef
|
|
60
|
-
})
|
|
62
|
+
}),
|
|
63
|
+
Margin
|
|
61
64
|
]);
|
|
62
65
|
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
export class ContentNormalizer {
|
|
2
2
|
static PARSER = new DOMParser();
|
|
3
|
-
static BLOCK_STYLES = ['text-align', 'line-height'];
|
|
4
3
|
static BLOCK_NODE_NAMES = ['P', 'H1', 'H2', 'H3', 'H4'];
|
|
5
4
|
|
|
5
|
+
static BLOCK_STYLES = [
|
|
6
|
+
'text-align',
|
|
7
|
+
'line-height',
|
|
8
|
+
'margin',
|
|
9
|
+
'margin-top',
|
|
10
|
+
'margin-bottom',
|
|
11
|
+
'margin-left',
|
|
12
|
+
'margin-right'
|
|
13
|
+
];
|
|
14
|
+
|
|
6
15
|
static ASSIGN_STYLE_RULES = [
|
|
7
16
|
{
|
|
8
17
|
tag: /^(b|strong)$/,
|
|
@@ -18,53 +27,67 @@ export class ContentNormalizer {
|
|
|
18
27
|
}
|
|
19
28
|
];
|
|
20
29
|
|
|
21
|
-
static
|
|
22
|
-
|
|
30
|
+
static build(content) {
|
|
31
|
+
return new ContentNormalizer({
|
|
32
|
+
parser: ContentNormalizer.PARSER,
|
|
33
|
+
content
|
|
34
|
+
});
|
|
35
|
+
}
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
static normalize(content) {
|
|
38
|
+
return ContentNormalizer.build(content).normalize();
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
constructor({ content, parser }) {
|
|
28
42
|
this._content = content;
|
|
29
43
|
this._parser = parser;
|
|
30
|
-
this.
|
|
44
|
+
this.dom = null;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
normalize() {
|
|
34
48
|
if (typeof this._content !== 'string') {
|
|
35
49
|
return this._content;
|
|
36
50
|
}
|
|
37
|
-
|
|
51
|
+
this.normalizeHTML();
|
|
52
|
+
return this.normalizedHTML;
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
this.
|
|
55
|
+
normalizeHTML() {
|
|
56
|
+
this.dom = this._parser.parseFromString(this._content.replace(/(\r)?\n/g, ''), 'text/html');
|
|
42
57
|
|
|
43
58
|
this._removeComments();
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
59
|
+
this.iterateNodes(this._normalizeBreakLines, (node) => node.tagName === 'BR');
|
|
60
|
+
this.iterateNodes(this._removeEmptyNodes, this._isBlockNode);
|
|
61
|
+
this.iterateNodes(this._normalizeListItems, (node) => node.tagName === 'LI');
|
|
62
|
+
this.iterateNodes(this._normalizeSettingsStructure, (node) => node.tagName === 'SPAN');
|
|
63
|
+
this.iterateNodes(this._normalizeStyles, (node) => node.tagName !== 'SPAN');
|
|
64
|
+
}
|
|
49
65
|
|
|
50
|
-
|
|
66
|
+
get normalizedHTML() {
|
|
67
|
+
return this.dom.body.innerHTML;
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
_removeComments() {
|
|
54
|
-
const iterator = this.
|
|
71
|
+
const iterator = this.createNodeIterator(NodeFilter.SHOW_COMMENT);
|
|
72
|
+
|
|
73
|
+
this.runIterator(iterator, (node) => node.remove());
|
|
74
|
+
}
|
|
55
75
|
|
|
56
|
-
|
|
76
|
+
createNodeIterator(whatToShow, filter) {
|
|
77
|
+
return this.dom.createNodeIterator(this.dom.body, whatToShow, filter);
|
|
57
78
|
}
|
|
58
79
|
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
80
|
+
iterateNodes(handler, condition = () => true) {
|
|
81
|
+
const checkCondition = (node) => node.tagName !== 'BODY' && condition.call(this, node);
|
|
82
|
+
|
|
83
|
+
const iterator = this.createNodeIterator(NodeFilter.SHOW_ELEMENT, {
|
|
84
|
+
acceptNode: (node) => checkCondition(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
|
|
62
85
|
});
|
|
63
86
|
|
|
64
|
-
this.
|
|
87
|
+
this.runIterator(iterator, handler);
|
|
65
88
|
}
|
|
66
89
|
|
|
67
|
-
|
|
90
|
+
runIterator(iterator, handler) {
|
|
68
91
|
let currentNode = iterator.nextNode();
|
|
69
92
|
|
|
70
93
|
while (currentNode) {
|
|
@@ -74,9 +97,7 @@ export class ContentNormalizer {
|
|
|
74
97
|
}
|
|
75
98
|
|
|
76
99
|
_removeEmptyNodes(node) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!html) node.remove();
|
|
100
|
+
if (!node.innerHTML.trim()) node.remove();
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
_normalizeListItems(itemEl) {
|
|
@@ -92,13 +92,6 @@ describe('normalize text content', () => {
|
|
|
92
92
|
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
test('should ignore non-breaking space only nodes', () => {
|
|
96
|
-
const input = '<p>lorem ipsum 1</p><p> </p><p>lorem ipsum 2</p>';
|
|
97
|
-
const output = '<p>lorem ipsum 1</p><p>lorem ipsum 2</p>';
|
|
98
|
-
|
|
99
|
-
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
95
|
test('should ignore newline chapters only nodes', () => {
|
|
103
96
|
const input = '<p>lorem ipsum 1</p><p>\n</p><p>lorem ipsum 2</p>';
|
|
104
97
|
const output = '<p>lorem ipsum 1</p><p>lorem ipsum 2</p>';
|
package/lib/styles/content.css
CHANGED
|
@@ -20,6 +20,14 @@
|
|
|
20
20
|
font-style: var(--zw-font-style, var(--zw-preset-font-style));
|
|
21
21
|
text-decoration: var(--zw-text-decoration, var(--zw-preset-text-decoration));
|
|
22
22
|
background-color: var(--zw-background-color, var(--zw-preset-background-color));
|
|
23
|
+
margin: var(--zw-margin);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
h1.zw-style.zw-style.zw-style,
|
|
27
|
+
h2.zw-style.zw-style.zw-style,
|
|
28
|
+
h3.zw-style.zw-style.zw-style,
|
|
29
|
+
h4.zw-style.zw-style.zw-style {
|
|
30
|
+
margin: var(--zw-margin, 8px 0);
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
@media (min-width: 1200px) {
|