aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts
blob: 8799964b39a9bd0a5d9e096f41d205ca0766d209 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { Node, DOMOutputSpec } from 'prosemirror-model';
import clamp from '../../../util/clamp';
import convertToCSSPTValue from '../../../util/convertToCSSPTValue';
import toCSSLineSpacing from '../../../util/toCSSLineSpacing';

// import type { NodeSpec } from './Types';
type NodeSpec = {
    attrs?: { [key: string]: any };
    content?: string;
    draggable?: boolean;
    group?: string;
    inline?: boolean;
    name?: string;
    parseDOM?: Array<any>;
    toDOM?: (node: any) => DOMOutputSpec;
};

// This assumes that every 36pt maps to one indent level.
export const INDENT_MARGIN_PT_SIZE = 36;
export const MIN_INDENT_LEVEL = 0;
export const MAX_INDENT_LEVEL = 7;
export const ATTRIBUTE_INDENT = 'data-indent';

export const EMPTY_CSS_VALUE = new Set(['', '0%', '0pt', '0px']);

const ALIGN_PATTERN = /(left|right|center|justify)/;

function convertMarginLeftToIndentValue(marginLeft: string): number {
    const ptValue = convertToCSSPTValue(marginLeft);
    return clamp(MIN_INDENT_LEVEL, Math.floor(ptValue / INDENT_MARGIN_PT_SIZE), MAX_INDENT_LEVEL);
}

function getAttrs(dom: HTMLElement): Object {
    const { lineHeight, textAlign, marginLeft, paddingTop, paddingBottom } = dom.style;

    let align = dom.getAttribute('align') || textAlign || '';
    align = ALIGN_PATTERN.test(align) ? align : '';

    let indent = parseInt(dom.getAttribute(ATTRIBUTE_INDENT) || '', 10);

    if (!indent && marginLeft) {
        indent = convertMarginLeftToIndentValue(marginLeft);
    }

    indent = indent || MIN_INDENT_LEVEL;

    const lineSpacing = lineHeight ? toCSSLineSpacing(lineHeight) : null;

    const id = dom.getAttribute('id') || '';
    return { align, indent, lineSpacing, paddingTop, paddingBottom, id };
}

function toDOM(node: Node): DOMOutputSpec {
    const { align, indent, inset, lineSpacing, paddingTop, paddingBottom, id } = node.attrs;
    const attrs: { [key: string]: any } | null = {};

    let style = '';
    if (align && align !== 'left') {
        style += `text-align: ${align};`;
    }

    if (lineSpacing) {
        const cssLineSpacing = toCSSLineSpacing(lineSpacing);
        style +=
            `line-height: ${cssLineSpacing};` +
            // This creates the local css variable `--czi-content-line-height`
            // that its children may apply.
            `--czi-content-line-height: ${cssLineSpacing}`;
    }

    if (paddingTop && !EMPTY_CSS_VALUE.has(paddingTop)) {
        style += `padding-top: ${paddingTop};`;
    }

    if (paddingBottom && !EMPTY_CSS_VALUE.has(paddingBottom)) {
        style += `padding-bottom: ${paddingBottom};`;
    }

    if (indent) {
        style += `text-indent: ${indent}; padding-left: ${indent < 0 ? -indent : undefined};`;
    }

    if (inset) {
        style += `margin-left: ${inset}; margin-right: ${inset};`;
    }

    style && (attrs.style = style);

    if (indent) {
        attrs[ATTRIBUTE_INDENT] = String(indent);
    }

    if (id) {
        attrs.id = id;
    }

    return ['p', attrs, 0];
}

// https://github.com/ProseMirror/prosemirror-schema-basic/blob/master/src/schema-basic.js
// :: NodeSpec A plain paragraph textblock. Represented in the DOM
// as a `<p>` element.
export const ParagraphNodeSpec: NodeSpec = {
    attrs: {
        align: { default: null },
        color: { default: null },
        id: { default: null },
        indent: { default: null },
        inset: { default: null },
        lineSpacing: { default: null },
        // TODO: Add UI to let user edit / clear padding.
        paddingBottom: { default: null },
        // TODO: Add UI to let user edit / clear padding.
        paddingTop: { default: null },
    },
    content: 'inline*',
    group: 'block',
    parseDOM: [{ tag: 'p', getAttrs }],
    toDOM,
};

export const toParagraphDOM = toDOM;
export const getParagraphNodeAttrs = getAttrs;

export default ParagraphNodeSpec;