visual-theme.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. import { EditorView } from '@codemirror/view'
  2. import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'
  3. import { tags } from '@lezer/highlight'
  4. /**
  5. * A syntax highlighter for content types that are only styled in the visual editor.
  6. */
  7. export const visualHighlightStyle = syntaxHighlighting(
  8. HighlightStyle.define([
  9. { tag: tags.link, class: 'ol-cm-link-text' },
  10. { tag: tags.url, class: 'ol-cm-url' },
  11. { tag: tags.typeName, class: 'ol-cm-monospace' },
  12. { tag: tags.attributeValue, class: 'ol-cm-monospace' },
  13. { tag: tags.keyword, class: 'ol-cm-monospace' },
  14. { tag: tags.string, class: 'ol-cm-monospace' },
  15. { tag: tags.punctuation, class: 'ol-cm-punctuation' },
  16. { tag: tags.literal, class: 'ol-cm-monospace' },
  17. {
  18. tag: tags.monospace,
  19. fontFamily: 'var(--source-font-family)',
  20. lineHeight: 1,
  21. overflowWrap: 'break-word',
  22. },
  23. ])
  24. )
  25. export const visualTheme = EditorView.theme({
  26. '&.cm-editor': {
  27. '--visual-font-family':
  28. "'Noto Serif', 'Palatino Linotype', 'Book Antiqua', Palatino, serif !important",
  29. '--visual-font-size': 'calc(var(--font-size) * 1.15)',
  30. '& .cm-content': {
  31. opacity: 0,
  32. },
  33. '&.ol-cm-parsed .cm-content': {
  34. opacity: 1,
  35. transition: 'opacity 0.1s ease-out',
  36. },
  37. },
  38. '.cm-content.cm-content': {
  39. overflowX: 'hidden', // needed so the callout elements don't overflow (requires line wrapping to be on)
  40. padding: '0 max(calc((100% - 100ch) / 2), 8%)', // max 100 characters per line
  41. fontFamily: 'var(--visual-font-family)',
  42. fontSize: 'var(--visual-font-size)',
  43. },
  44. '.cm-cursor-primary.cm-cursor-primary': {
  45. fontFamily: 'var(--visual-font-family)',
  46. fontSize: 'var(--visual-font-size)',
  47. },
  48. '.cm-line': {
  49. overflowX: 'visible', // needed so the callout elements can overflow when the content has padding
  50. },
  51. '.cm-gutter': {
  52. opacity: '0.5',
  53. },
  54. '.cm-tooltip': {
  55. fontSize: 'calc(var(--font-size) * 1.15) !important',
  56. },
  57. '.ol-cm-link-text': {
  58. textDecoration: 'underline',
  59. fontFamily: 'inherit',
  60. },
  61. '.ol-cm-monospace': {
  62. fontFamily: 'var(--source-font-family)',
  63. lineHeight: 1,
  64. fontWeight: 'normal',
  65. fontStyle: 'normal',
  66. fontVariant: 'normal',
  67. textDecoration: 'none',
  68. },
  69. '.ol-cm-punctuation': {
  70. fontFamily: 'var(--source-font-family)',
  71. lineHeight: 1,
  72. },
  73. '.ol-cm-brace': {
  74. opacity: '0.5',
  75. },
  76. '.ol-cm-math': {
  77. overflow: 'hidden', // stop the margin from the inner math element affecting the block height
  78. },
  79. '.ol-cm-maketitle': {
  80. textAlign: 'center',
  81. paddingBottom: '2em',
  82. },
  83. '.ol-cm-title': {
  84. fontSize: '1.7em',
  85. cursor: 'pointer',
  86. padding: '0.5em',
  87. lineHeight: 'calc(var(--line-height) * 5/6)',
  88. textWrap: 'balance',
  89. },
  90. '.ol-cm-authors': {
  91. display: 'flex',
  92. justifyContent: 'space-evenly',
  93. gap: '0.5em',
  94. flexWrap: 'wrap',
  95. },
  96. '.ol-cm-author': {
  97. cursor: 'pointer',
  98. display: 'inline-block',
  99. minWidth: '150px',
  100. },
  101. '.ol-cm-icon-brace': {
  102. filter: 'grayscale(1)',
  103. marginRight: '2px',
  104. },
  105. '.ol-cm-indicator': {
  106. color: 'rgba(125, 125, 125, 0.5)',
  107. },
  108. '.ol-cm-begin': {
  109. fontFamily: 'var(--source-font-family)',
  110. minHeight: '1em',
  111. textAlign: 'center',
  112. display: 'flex',
  113. alignItems: 'center',
  114. justifyContent: 'center',
  115. },
  116. '.ol-cm-end': {
  117. fontFamily: 'var(--source-font-family)',
  118. paddingBottom: '1.5em',
  119. minHeight: '1em',
  120. textAlign: 'center',
  121. justifyContent: 'center',
  122. 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))`,
  123. },
  124. '.ol-cm-environment-top': {
  125. paddingTop: '1em',
  126. },
  127. '.ol-cm-environment-bottom': {
  128. paddingBottom: '1em',
  129. },
  130. '.ol-cm-environment-first-line': {
  131. paddingTop: '0.5em !important',
  132. borderTopLeftRadius: '8px',
  133. borderTopRightRadius: '8px',
  134. },
  135. '.ol-cm-environment-last-line': {
  136. paddingBottom: '1em !important',
  137. borderBottomLeftRadius: '8px',
  138. borderBottomRightRadius: '8px',
  139. },
  140. '.ol-cm-environment-figure.ol-cm-environment-line, .ol-cm-environment-table.ol-cm-environment-line':
  141. {
  142. backgroundColor: 'rgba(125, 125, 125, 0.05)',
  143. padding: '0 12px',
  144. },
  145. '.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':
  146. {
  147. boxShadow: '0 2px 5px -3px rgb(125, 125, 125, 0.5)',
  148. },
  149. '.ol-cm-environment-theorem-plain': {
  150. fontStyle: 'italic',
  151. },
  152. '.ol-cm-begin-proof > .ol-cm-environment-name': {
  153. fontStyle: 'italic',
  154. },
  155. '.ol-cm-environment-quote-block.ol-cm-environment-line': {
  156. borderLeft: '4px solid rgba(125, 125, 125, 0.25)',
  157. paddingLeft: '1em',
  158. borderRadius: '0',
  159. },
  160. '.ol-cm-environment-padding': {
  161. flex: 1,
  162. height: '1px',
  163. 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))`,
  164. },
  165. '.ol-cm-environment-name': {
  166. padding: '0 1em',
  167. },
  168. '.ol-cm-begin-abstract > .ol-cm-environment-name': {
  169. fontFamily: 'var(--visual-font-family)',
  170. fontSize: '1.2em',
  171. fontWeight: 550,
  172. textTransform: 'capitalize',
  173. },
  174. '.ol-cm-begin-theorem > .ol-cm-environment-name': {
  175. fontFamily: 'var(--visual-font-family)',
  176. fontWeight: 550,
  177. padding: '0 6px',
  178. textTransform: 'capitalize',
  179. },
  180. '.ol-cm-begin-theorem > .ol-cm-environment-padding:first-of-type': {
  181. flex: 0,
  182. },
  183. '.ol-cm-item': {
  184. paddingInlineStart: 'calc(var(--list-depth) * 2ch)',
  185. },
  186. '.ol-cm-item::before': {
  187. counterReset: 'list-item var(--list-ordinal)',
  188. content: 'counter(list-item, var(--list-type)) var(--list-suffix)',
  189. },
  190. '.ol-cm-heading': {
  191. fontWeight: 550,
  192. lineHeight: '1.35',
  193. color: 'inherit !important',
  194. background: 'inherit !important',
  195. },
  196. '.ol-cm-command-part': {
  197. fontSize: '2em',
  198. },
  199. '.ol-cm-command-chapter': {
  200. fontSize: '1.6em',
  201. },
  202. '.ol-cm-command-section': {
  203. fontSize: '1.44em',
  204. },
  205. '.ol-cm-command-subsection': {
  206. fontSize: '1.2em',
  207. },
  208. '.ol-cm-command-subsubsection': {
  209. fontSize: '1em',
  210. },
  211. '.ol-cm-command-paragraph': {
  212. fontSize: '1em',
  213. },
  214. '.ol-cm-command-subparagraph': {
  215. fontSize: '1em',
  216. },
  217. '.ol-cm-frame-title': {
  218. fontSize: '1.44em',
  219. },
  220. '.ol-cm-frame-subtitle': {
  221. fontSize: '1em',
  222. },
  223. '.ol-cm-divider': {
  224. borderBottom: '1px solid rgba(125, 125, 125, 0.1)',
  225. padding: '0.5em 6px',
  226. '&.ol-cm-frame-widget': {
  227. borderBottom: 'none',
  228. borderTop: '1px solid rgba(125, 125, 125, 0.1)',
  229. },
  230. },
  231. '.ol-cm-command-textbf': {
  232. fontWeight: 700,
  233. },
  234. '.ol-cm-command-textit': {
  235. fontStyle: 'italic',
  236. },
  237. '.ol-cm-command-textsc': {
  238. fontVariant: 'small-caps',
  239. },
  240. '.ol-cm-command-texttt': {
  241. fontFamily: 'monospace',
  242. },
  243. '.ol-cm-command-textmd, .ol-cm-command-textmd > .ol-cm-command-textbf': {
  244. fontWeight: 'normal',
  245. },
  246. '.ol-cm-command-textsf': {
  247. fontFamily: 'var(--source-font-family)',
  248. },
  249. '.ol-cm-command-textsuperscript': {
  250. verticalAlign: 'super',
  251. fontSize: 'smaller',
  252. lineHeight: 'calc(var(--line-height) / 2)',
  253. },
  254. '.ol-cm-command-textsubscript': {
  255. verticalAlign: 'sub',
  256. fontSize: 'smaller',
  257. lineHeight: 'calc(var(--line-height) / 2)',
  258. },
  259. '.ol-cm-command-underline': {
  260. textDecoration: 'underline',
  261. },
  262. '.ol-cm-command-sout': {
  263. textDecoration: 'line-through',
  264. },
  265. '.ol-cm-command-emph': {
  266. fontStyle: 'italic',
  267. '& .ol-cm-command-textit': {
  268. fontStyle: 'normal',
  269. },
  270. '.ol-cm-command-textit &': {
  271. fontStyle: 'normal',
  272. },
  273. },
  274. '.ol-cm-command-url': {
  275. textDecoration: 'underline',
  276. // copied from tags.monospace
  277. fontFamily: 'var(--source-font-family)',
  278. lineHeight: 1,
  279. overflowWrap: 'break-word',
  280. hyphens: 'auto',
  281. },
  282. '.ol-cm-environment-centered': {
  283. '&.ol-cm-label-line, &.ol-cm-caption-line': {
  284. textAlign: 'center',
  285. },
  286. '&.ol-cm-caption-line': {
  287. padding: '0 10%',
  288. },
  289. },
  290. '.ol-cm-caption-line .ol-cm-label': {
  291. marginRight: '1ch',
  292. },
  293. '.ol-cm-environment-verbatim': {
  294. fontFamily: 'var(--source-font-family)',
  295. },
  296. '.ol-cm-environment-lstlisting': {
  297. fontFamily: 'var(--source-font-family)',
  298. },
  299. '.ol-cm-tex': {
  300. textTransform: 'uppercase',
  301. '& sup': {
  302. position: 'inherit',
  303. fontSize: '0.85em',
  304. verticalAlign: '0.15em',
  305. marginLeft: '-0.36em',
  306. marginRight: '-0.15em',
  307. },
  308. '& sub': {
  309. position: 'inherit',
  310. fontSize: '1em',
  311. verticalAlign: '-0.5ex',
  312. marginLeft: '-0.1667em',
  313. marginRight: '-0.125em',
  314. },
  315. },
  316. '.ol-cm-graphics': {
  317. display: 'block',
  318. maxWidth: 'min(300px, 100%)',
  319. paddingTop: '1em',
  320. paddingBottom: '1em',
  321. cursor: 'pointer',
  322. '.ol-cm-graphics-inline &': {
  323. display: 'inline',
  324. },
  325. },
  326. '.ol-cm-graphics-inline-edit-wrapper': {
  327. display: 'inline-block',
  328. position: 'relative',
  329. verticalAlign: 'middle',
  330. '& .ol-cm-graphics': {
  331. paddingTop: 0,
  332. paddingBottom: 0,
  333. },
  334. },
  335. '.ol-cm-graphics-loading': {
  336. height: '300px', // guess that the height is the same as the max width
  337. },
  338. '.ol-cm-graphics-error': {
  339. border: '1px solid red',
  340. padding: '8px',
  341. },
  342. '.ol-cm-environment-centered .ol-cm-graphics': {
  343. margin: '0 auto',
  344. },
  345. '.ol-cm-command-verb .ol-cm-monospace': {
  346. color: 'inherit', // remove syntax highlighting colour from verbatim content
  347. },
  348. '.ol-cm-preamble-wrapper': {
  349. padding: '0.5em 0',
  350. '&.ol-cm-preamble-expanded': {
  351. paddingBottom: '0',
  352. },
  353. },
  354. '.ol-cm-preamble-widget, .ol-cm-end-document-widget': {
  355. padding: '0.25em 1em',
  356. borderRadius: '8px',
  357. fontFamily: '"Lato", sans-serif',
  358. fontSize: '14px',
  359. '.ol-cm-preamble-expanded &': {
  360. borderBottomLeftRadius: '0',
  361. borderBottomRightRadius: '0',
  362. borderBottom: '1px solid rgba(125, 125, 125, 0.2)',
  363. },
  364. },
  365. '.ol-cm-preamble-widget': {
  366. cursor: 'pointer',
  367. display: 'flex',
  368. justifyContent: 'space-between',
  369. alignItems: 'center',
  370. },
  371. '.ol-cm-preamble-expand-icon': {
  372. width: '32px',
  373. lineHeight: '32px',
  374. textAlign: 'center',
  375. transition: '0.2s ease-out',
  376. opacity: '0.5',
  377. '.ol-cm-preamble-widget:hover &': {
  378. opacity: '1',
  379. },
  380. '.ol-cm-preamble-expanded &': {
  381. transform: 'rotate(180deg)',
  382. },
  383. },
  384. '.ol-cm-preamble-line, .ol-cm-end-document-widget, .ol-cm-preamble-widget': {
  385. backgroundColor: 'rgba(125, 125, 125, 0.05)',
  386. },
  387. '.ol-cm-preamble-line': {
  388. padding: '0 12px',
  389. '&.ol-cm-environment-first-line': {
  390. borderRadius: '0',
  391. },
  392. },
  393. '.ol-cm-end-document-widget': {
  394. textAlign: 'center',
  395. },
  396. '.ol-cm-environment-figure': {
  397. position: 'relative',
  398. },
  399. '.ol-cm-graphics-edit-button': {
  400. position: 'absolute',
  401. top: '18px',
  402. right: '18px',
  403. },
  404. '.ol-cm-footnote': {
  405. display: 'inline-flex',
  406. padding: '0 0.1em',
  407. background: 'rgba(125, 125, 125, 0.25)',
  408. borderRadius: '2px',
  409. height: '1em',
  410. cursor: 'pointer',
  411. verticalAlign: 'text-top',
  412. '&:not(.ol-cm-footnote-view):hover': {
  413. background: 'rgba(125, 125, 125, 0.5)',
  414. },
  415. '&.ol-cm-footnote-view': {
  416. height: 'auto',
  417. verticalAlign: 'unset',
  418. display: 'inline',
  419. padding: '0 0.5em',
  420. },
  421. },
  422. })