import * as React from 'react'; import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model'; import { Doc } from '../../../../fields/Doc'; import { Utils } from '../../../../Utils'; const emDOM: DOMOutputSpec = ['em', 0]; const strongDOM: DOMOutputSpec = ['strong', 0]; const codeDOM: DOMOutputSpec = ['code', 0]; // :: Object [Specs](#model.MarkSpec) for the marks in the schema. export const marks: { [index: string]: MarkSpec } = { splitter: { attrs: { id: { default: '' }, }, toDOM(node: any) { return ['div', { className: 'dummy' }, 0]; }, }, // :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the // published document's title. // NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source. This is because // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since // multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to // disambiguate links from one another. // Rendered and parsed as an `` // element. autoLinkAnchor: { attrs: { allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, title: { default: null }, }, inclusive: false, parseDOM: [ { tag: 'a[href]', getAttrs(dom: any) { return { title: dom.getAttribute('title'), }; }, }, ], toDOM(node: any) { const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), ''); const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0]; }, }, noAutoLinkAnchor: { attrs: {}, inclusive: false, parseDOM: [ { tag: 'div', getAttrs(dom: any) { return { noAutoLink: dom.getAttribute('data-noAutoLink'), }; }, }, ], toDOM(node: any) { return ['span', { 'data-noAutoLink': 'true' }, 0]; }, }, // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text, // and a title for use in menus and hover. `title` // defaults to the empty string. Rendered and parsed as an `` // element. linkAnchor: { attrs: { allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, title: { default: null }, noPreview: { default: false }, fontSize: { default: null }, docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text }, inclusive: false, parseDOM: [ { tag: 'a[href]', getAttrs(dom: any) { return { title: dom.getAttribute('title'), noPreview: dom.getAttribute('noPreview'), }; }, }, ], toDOM(node: any) { const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), ''); const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); return node.attrs.docref && node.attrs.title ? [ 'a', ['span', 0], [ 'span', { ...node.attrs, class: 'prosemirror-attribution', 'data-targethrefs': targethrefs, href: node.attrs.allAnchors[0].href, style: `font-size: ${node.attrs.fontSize}`, }, node.attrs.title, ], ] : ['a', { id: '' + Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0]; }, }, /** FONT SIZES */ pFontSize: { attrs: { fontSize: { default: '10px' } }, parseDOM: [ { tag: 'span', getAttrs(dom: any) { return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : '' }; }, }, ], toDOM: node => (node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]), }, /* FONTS */ pFontFamily: { attrs: { family: { default: '' } }, parseDOM: [ { tag: 'span', getAttrs(dom: any) { const cstyle = getComputedStyle(dom); if (cstyle.font) { if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' }; if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' }; if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' }; if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' }; if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' }; if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' }; } return { family: '' }; }, }, ], toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]), }, // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. pFontColor: { attrs: { color: { default: '' } }, inclusive: true, parseDOM: [ { tag: 'span', getAttrs(dom: any) { return { color: dom.getAttribute('color') }; }, }, ], toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]), }, marker: { attrs: { highlight: { default: 'transparent' }, }, inclusive: true, parseDOM: [ { tag: 'span', getAttrs(dom: any) { return { highlight: dom.getAttribute('backgroundColor') }; }, }, ], toDOM(node: any) { return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; }, }, // :: MarkSpec An emphasis mark. Rendered as an `` element. // Has parse rules that also match `` and `font-style: italic`. em: { parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style: italic' }], toDOM() { return emDOM; }, }, // :: MarkSpec A strong mark. Rendered as ``, parse rules // also match `` and `font-weight: bold`. strong: { parseDOM: [{ tag: 'strong' }, { tag: 'b' }, { style: 'font-weight' }], toDOM() { return strongDOM; }, }, strikethrough: { parseDOM: [{ tag: 'strike' }, { style: 'text-decoration=line-through' }, { style: 'text-decoration-line=line-through' }], toDOM: () => [ 'span', { style: 'text-decoration-line:line-through', }, ], }, subscript: { excludes: 'superscript', parseDOM: [{ tag: 'sub' }, { style: 'vertical-align=sub' }], toDOM: () => ['sub'], }, superscript: { excludes: 'subscript', parseDOM: [{ tag: 'sup' }, { style: 'vertical-align=super' }], toDOM: () => ['sup'], }, mbulletType: { attrs: { bulletType: { default: 'decimal' }, }, toDOM(node: any) { return [ 'span', { style: `background: ${node.attrs.bulletType === 'decimal' ? 'yellow' : node.attrs.bulletType === 'upper-alpha' ? 'blue' : 'green'}`, }, ]; }, }, summarizeInclusive: { parseDOM: [ { tag: 'span', getAttrs: (p: any) => { if (typeof p !== 'string') { const style = getComputedStyle(p); if (style.textDecoration === 'underline') return null; if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: solid') !== -1) { return null; } } return false; }, }, ], inclusive: true, toDOM() { return [ 'span', { style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)', }, ]; }, }, summarize: { inclusive: false, parseDOM: [ { tag: 'span', getAttrs: (p: any) => { if (typeof p !== 'string') { const style = getComputedStyle(p); if (style.textDecoration === 'underline') return null; if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: dotted') !== -1) { return null; } } return false; }, }, ], toDOM() { return [ 'span', { style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)', }, ]; }, }, underline: { parseDOM: [ { tag: 'span', getAttrs: (p: any) => { if (typeof p !== 'string') { const style = getComputedStyle(p); if (style.textDecoration === 'underline' || p.parentElement.outerHTML.indexOf('text-decoration-style:line') !== -1) { return null; } } return false; }, }, // { style: "text-decoration=underline" } ], toDOM: () => [ 'span', { style: 'text-decoration:underline;text-decoration-style:line', }, ], }, search_highlight: { attrs: { selected: { default: false }, }, parseDOM: [{ style: 'background: yellow' }], toDOM(node: any) { return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'yellow'}` }]; }, }, // the id of the user who entered the text user_mark: { attrs: { userid: { default: '' }, modified: { default: 'when?' }, // 1 second intervals since 1970 }, excludes: 'user_mark', group: 'inline', toDOM(node: any) { const uid = node.attrs.userid.replace(/\./g, '').replace(/@/g, ''); const min = Math.round(node.attrs.modified / 60); const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : ''; return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0]; }, }, // the id of the user who entered the text user_tag: { attrs: { userid: { default: '' }, modified: { default: 'when?' }, // 1 second intervals since 1970 tag: { default: '' }, }, group: 'inline', inclusive: false, toDOM(node: any) { const uid = node.attrs.userid.replace('.', '').replace('@', ''); return ['span', { class: 'UT-' + uid + ' UT-' + node.attrs.tag }, 0]; }, }, // :: MarkSpec Code font mark. Represented as a `` element. code: { parseDOM: [{ tag: 'code' }], toDOM() { return codeDOM; }, }, };