| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- import { EditorView } from '@codemirror/view'
- import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'
- import { tags } from '@lezer/highlight'
- import { Annotation, Compartment, Extension, Facet } from '@codemirror/state'
- /**
- * A syntax highlighter for content types that are only styled in the visual editor.
- */
- export const visualHighlightStyle = syntaxHighlighting(
- HighlightStyle.define([
- { tag: tags.link, class: 'ol-cm-link-text' },
- { tag: tags.url, class: 'ol-cm-url' },
- { tag: tags.typeName, class: 'ol-cm-monospace' },
- { tag: tags.attributeValue, class: 'ol-cm-monospace' },
- { tag: tags.keyword, class: 'ol-cm-monospace' },
- { tag: tags.string, class: 'ol-cm-monospace' },
- { tag: tags.punctuation, class: 'ol-cm-punctuation' },
- { tag: tags.literal, class: 'ol-cm-monospace' },
- {
- tag: tags.monospace,
- fontFamily: 'var(--source-font-family)',
- lineHeight: 1,
- overflowWrap: 'break-word',
- },
- ])
- )
- const mainVisualTheme = EditorView.theme({
- '&.cm-editor': {
- '--visual-font-family':
- "'Noto Serif', 'Palatino Linotype', 'Book Antiqua', Palatino, serif !important",
- '--visual-font-size': 'calc(var(--font-size) * 1.15)',
- '& .cm-content': {
- opacity: 0,
- },
- '&.ol-cm-parsed .cm-content': {
- opacity: 1,
- transition: 'opacity 0.1s ease-out',
- },
- },
- '.cm-content.cm-content': {
- overflowX: 'hidden', // needed so the callout elements don't overflow (requires line wrapping to be on)
- padding:
- '0 max(calc((var(--content-width) - 95ch) / 2), calc(var(--content-width) * 0.08))', // max 95 characters per line
- fontFamily: 'var(--visual-font-family)',
- fontSize: 'var(--visual-font-size)',
- },
- '.cm-cursor-primary.cm-cursor-primary': {
- fontFamily: 'var(--visual-font-family)',
- fontSize: 'var(--visual-font-size)',
- },
- '.cm-line': {
- overflowX: 'visible', // needed so the callout elements can overflow when the content has padding
- },
- '.cm-gutter': {
- opacity: '0.5',
- },
- '.cm-tooltip': {
- fontSize: 'calc(var(--font-size) * 1.15) !important',
- },
- '.ol-cm-link-text': {
- textDecoration: 'underline',
- fontFamily: 'inherit',
- },
- '.ol-cm-monospace': {
- fontFamily: 'var(--source-font-family)',
- lineHeight: 1,
- fontWeight: 'normal',
- fontStyle: 'normal',
- fontVariant: 'normal',
- textDecoration: 'none',
- },
- '.ol-cm-punctuation': {
- fontFamily: 'var(--source-font-family)',
- lineHeight: 1,
- },
- '.ol-cm-brace': {
- opacity: '0.5',
- },
- '.ol-cm-math': {
- overflow: 'hidden', // stop the margin from the inner math element affecting the block height
- },
- '.ol-cm-maketitle': {
- textAlign: 'center',
- paddingBottom: '2em',
- },
- '.ol-cm-title': {
- fontSize: '1.7em',
- cursor: 'pointer',
- padding: '0.5em',
- lineHeight: 'calc(var(--line-height) * 5/6)',
- textWrap: 'balance',
- },
- '.ol-cm-authors': {
- display: 'flex',
- justifyContent: 'space-evenly',
- gap: '0.5em',
- flexWrap: 'wrap',
- },
- '.ol-cm-author': {
- cursor: 'pointer',
- display: 'inline-block',
- minWidth: '150px',
- },
- '.ol-cm-icon-brace': {
- filter: 'grayscale(1)',
- marginRight: '2px',
- },
- '.ol-cm-indicator': {
- color: 'rgba(125, 125, 125, 0.5)',
- },
- '.ol-cm-begin': {
- fontFamily: 'var(--source-font-family)',
- minHeight: '1em',
- textAlign: 'center',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- },
- '.ol-cm-end': {
- fontFamily: 'var(--source-font-family)',
- paddingBottom: '1.5em',
- minHeight: '1em',
- textAlign: 'center',
- justifyContent: 'center',
- background: `linear-gradient(180deg, rgba(0,0,0,0) calc(50% - 1px), rgba(192,192,192,1) calc(50%), rgba(0,0,0,0) calc(50% + 1px))`,
- },
- '.ol-cm-environment-top': {
- paddingTop: '1em',
- },
- '.ol-cm-environment-bottom': {
- paddingBottom: '1em',
- },
- '.ol-cm-environment-first-line': {
- paddingTop: '0.5em !important',
- borderTopLeftRadius: '8px',
- borderTopRightRadius: '8px',
- },
- '.ol-cm-environment-last-line': {
- paddingBottom: '1em !important',
- borderBottomLeftRadius: '8px',
- borderBottomRightRadius: '8px',
- },
- '.ol-cm-environment-figure.ol-cm-environment-line, .ol-cm-environment-table.ol-cm-environment-line':
- {
- backgroundColor: 'rgba(125, 125, 125, 0.05)',
- padding: '0 12px',
- },
- '.ol-cm-environment-figure.ol-cm-environment-last-line, .ol-cm-environment-table.ol-cm-environment-last-line, .ol-cm-preamble-line.ol-cm-environment-last-line':
- {
- boxShadow: '0 2px 5px -3px rgb(125, 125, 125, 0.5)',
- },
- '.ol-cm-environment-theorem-plain': {
- fontStyle: 'italic',
- },
- '.ol-cm-begin-proof > .ol-cm-environment-name': {
- fontStyle: 'italic',
- },
- '.ol-cm-environment-quote-block.ol-cm-environment-line': {
- borderLeft: '4px solid rgba(125, 125, 125, 0.25)',
- paddingLeft: '1em',
- borderRadius: '0',
- },
- '.ol-cm-environment-padding': {
- flex: 1,
- height: '1px',
- background: `linear-gradient(180deg, rgba(0,0,0,0) calc(50% - 1px), rgba(192,192,192,1) calc(50%), rgba(0,0,0,0) calc(50% + 1px))`,
- },
- '.ol-cm-environment-name': {
- padding: '0 1em',
- },
- '.ol-cm-begin-abstract > .ol-cm-environment-name': {
- fontFamily: 'var(--visual-font-family)',
- fontSize: '1.2em',
- fontWeight: 550,
- textTransform: 'capitalize',
- },
- '.ol-cm-begin-theorem > .ol-cm-environment-name': {
- fontFamily: 'var(--visual-font-family)',
- fontWeight: 550,
- padding: '0 6px',
- textTransform: 'capitalize',
- },
- '.ol-cm-begin-theorem > .ol-cm-environment-padding:first-of-type': {
- flex: 0,
- },
- '.ol-cm-item': {
- paddingInlineStart: 'calc(var(--list-depth) * 2ch)',
- },
- '.ol-cm-item::before': {
- counterReset: 'list-item var(--list-ordinal)',
- content: 'counter(list-item, var(--list-type)) var(--list-suffix)',
- },
- '.ol-cm-heading': {
- fontWeight: 550,
- lineHeight: '1.35',
- color: 'inherit !important',
- background: 'inherit !important',
- },
- '.ol-cm-command-part': {
- fontSize: '2em',
- },
- '.ol-cm-command-chapter': {
- fontSize: '1.6em',
- },
- '.ol-cm-command-section': {
- fontSize: '1.44em',
- },
- '.ol-cm-command-subsection': {
- fontSize: '1.2em',
- },
- '.ol-cm-command-subsubsection': {
- fontSize: '1em',
- },
- '.ol-cm-command-paragraph': {
- fontSize: '1em',
- },
- '.ol-cm-command-subparagraph': {
- fontSize: '1em',
- },
- '.ol-cm-frame-title': {
- fontSize: '1.44em',
- },
- '.ol-cm-frame-subtitle': {
- fontSize: '1em',
- },
- '.ol-cm-divider': {
- borderBottom: '1px solid rgba(125, 125, 125, 0.1)',
- padding: '0.5em 6px',
- '&.ol-cm-frame-widget': {
- borderBottom: 'none',
- borderTop: '1px solid rgba(125, 125, 125, 0.1)',
- },
- },
- '.ol-cm-command-textbf': {
- fontWeight: 700,
- },
- '.ol-cm-command-textit': {
- fontStyle: 'italic',
- },
- '.ol-cm-command-textsc': {
- fontVariant: 'small-caps',
- },
- '.ol-cm-command-texttt': {
- fontFamily: 'monospace',
- },
- '.ol-cm-command-textmd, .ol-cm-command-textmd > .ol-cm-command-textbf': {
- fontWeight: 'normal',
- },
- '.ol-cm-command-textsf': {
- fontFamily: 'var(--source-font-family)',
- },
- '.ol-cm-command-textsuperscript': {
- verticalAlign: 'super',
- fontSize: 'smaller',
- lineHeight: 'calc(var(--line-height) / 2)',
- },
- '.ol-cm-command-textsubscript': {
- verticalAlign: 'sub',
- fontSize: 'smaller',
- lineHeight: 'calc(var(--line-height) / 2)',
- },
- '.ol-cm-command-underline': {
- textDecoration: 'underline',
- },
- '.ol-cm-command-sout': {
- textDecoration: 'line-through',
- },
- '.ol-cm-command-emph': {
- fontStyle: 'italic',
- '& .ol-cm-command-textit': {
- fontStyle: 'normal',
- },
- '.ol-cm-command-textit &': {
- fontStyle: 'normal',
- },
- },
- '.ol-cm-command-url': {
- textDecoration: 'underline',
- // copied from tags.monospace
- fontFamily: 'var(--source-font-family)',
- lineHeight: 1,
- overflowWrap: 'break-word',
- hyphens: 'auto',
- },
- '.ol-cm-environment-centered': {
- '&.ol-cm-label-line, &.ol-cm-caption-line': {
- textAlign: 'center',
- },
- '&.ol-cm-caption-line': {
- padding: '0 10%',
- },
- },
- '.ol-cm-caption-line .ol-cm-label': {
- marginRight: '1ch',
- },
- '.ol-cm-environment-verbatim': {
- fontFamily: 'var(--source-font-family)',
- },
- '.ol-cm-environment-lstlisting': {
- fontFamily: 'var(--source-font-family)',
- },
- '.ol-cm-tex': {
- textTransform: 'uppercase',
- '& sup': {
- position: 'inherit',
- fontSize: '0.85em',
- verticalAlign: '0.15em',
- marginLeft: '-0.36em',
- marginRight: '-0.15em',
- },
- '& sub': {
- position: 'inherit',
- fontSize: '1em',
- verticalAlign: '-0.5ex',
- marginLeft: '-0.1667em',
- marginRight: '-0.125em',
- },
- },
- '.ol-cm-graphics': {
- display: 'block',
- maxWidth: 'min(300px, 100%)',
- paddingTop: '1em',
- paddingBottom: '1em',
- cursor: 'pointer',
- '.ol-cm-graphics-inline &': {
- display: 'inline',
- },
- },
- '.ol-cm-graphics-inline-edit-wrapper': {
- display: 'inline-block',
- position: 'relative',
- verticalAlign: 'middle',
- '& .ol-cm-graphics': {
- paddingTop: 0,
- paddingBottom: 0,
- },
- },
- '.ol-cm-graphics-loading': {
- height: '300px', // guess that the height is the same as the max width
- },
- '.ol-cm-graphics-error': {
- border: '1px solid red',
- padding: '8px',
- },
- '.ol-cm-environment-centered .ol-cm-graphics': {
- margin: '0 auto',
- },
- '.ol-cm-command-verb .ol-cm-monospace': {
- color: 'inherit', // remove syntax highlighting colour from verbatim content
- },
- '.ol-cm-preamble-wrapper': {
- padding: '0.5em 0',
- '&.ol-cm-preamble-expanded': {
- paddingBottom: '0',
- },
- },
- '.ol-cm-preamble-widget, .ol-cm-end-document-widget': {
- padding: '0.25em 1em',
- borderRadius: '8px',
- fontFamily: '"Lato", sans-serif',
- fontSize: '14px',
- '.ol-cm-preamble-expanded &': {
- borderBottomLeftRadius: '0',
- borderBottomRightRadius: '0',
- borderBottom: '1px solid rgba(125, 125, 125, 0.2)',
- },
- },
- '.ol-cm-preamble-widget': {
- cursor: 'pointer',
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- },
- '.ol-cm-preamble-expand-icon': {
- width: '32px',
- lineHeight: '32px',
- textAlign: 'center',
- transition: '0.2s ease-out',
- opacity: '0.5',
- '.ol-cm-preamble-widget:hover &': {
- opacity: '1',
- },
- '.ol-cm-preamble-expanded &': {
- transform: 'rotate(180deg)',
- },
- },
- '.ol-cm-preamble-line, .ol-cm-end-document-widget, .ol-cm-preamble-widget': {
- backgroundColor: 'rgba(125, 125, 125, 0.05)',
- },
- '.ol-cm-preamble-line': {
- padding: '0 12px',
- '&.ol-cm-environment-first-line': {
- borderRadius: '0',
- },
- },
- '.ol-cm-end-document-widget': {
- textAlign: 'center',
- },
- '.ol-cm-environment-figure': {
- position: 'relative',
- },
- '.ol-cm-graphics-edit-button': {
- position: 'absolute',
- top: '18px',
- right: '18px',
- },
- '.ol-cm-footnote': {
- display: 'inline-flex',
- padding: '0 0.1em',
- background: 'rgba(125, 125, 125, 0.25)',
- borderRadius: '2px',
- height: '1em',
- cursor: 'pointer',
- verticalAlign: 'text-top',
- '&:not(.ol-cm-footnote-view):hover': {
- background: 'rgba(125, 125, 125, 0.5)',
- },
- '&.ol-cm-footnote-view': {
- height: 'auto',
- verticalAlign: 'unset',
- display: 'inline',
- padding: '0 0.5em',
- },
- },
- })
- const contentWidthThemeConf = new Compartment()
- const changeContentWidthAnnotation = Annotation.define<boolean>()
- const currentWidth = Facet.define<string, string>({
- combine: values => {
- if (values.length === 0) {
- return ''
- }
- return values[0]
- },
- })
- function createContentWidthTheme(contentWidth: string) {
- return [
- currentWidth.of(contentWidth),
- EditorView.editorAttributes.of({
- style: `--content-width: ${contentWidth}`,
- }),
- ]
- }
- const contentWidthSetter = EditorView.updateListener.of(update => {
- if (update.geometryChanged && !update.docChanged) {
- // Ignore any update triggered by this plugin
- if (
- update.transactions.some(tr =>
- tr.annotation(changeContentWidthAnnotation)
- )
- ) {
- return
- }
- const viewWidth = `${update.view.contentDOM.offsetWidth}px`
- const currentConfiguredWidth = update.state.facet(currentWidth)
- if (currentConfiguredWidth === viewWidth) {
- // We already have the correct width stored
- return
- }
- update.view.dispatch({
- effects: contentWidthThemeConf.reconfigure(
- createContentWidthTheme(viewWidth)
- ),
- // Set the selection explicitly to force the cursor to redraw if CM6
- // fails to spot a change in geometry, which sometimes seems to happen
- // (see #15145)
- selection: update.view.state.selection,
- annotations: changeContentWidthAnnotation.of(true),
- })
- }
- })
- export const visualTheme: Extension = [
- contentWidthThemeConf.of(createContentWidthTheme('100%')),
- mainVisualTheme,
- contentWidthSetter,
- ]
|