EditorMarkdown.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. <template lang="pug">
  2. .editor-markdown
  3. .editor-markdown-main
  4. .editor-markdown-sidebar
  5. //--------------------------------------------------------
  6. //- SIDE TOOLBAR
  7. //--------------------------------------------------------
  8. q-btn(
  9. icon='mdi-link-variant-plus'
  10. padding='sm sm'
  11. flat
  12. )
  13. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertLink') }}
  14. q-btn(
  15. icon='mdi-image-plus-outline'
  16. padding='sm sm'
  17. flat
  18. @click='insertAssets'
  19. )
  20. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertAssets') }}
  21. q-btn(
  22. icon='mdi-code-json'
  23. padding='sm sm'
  24. flat
  25. )
  26. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertCodeBlock') }}
  27. q-btn(
  28. icon='mdi-table-large-plus'
  29. padding='sm sm'
  30. flat
  31. @click='insertTable'
  32. )
  33. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertTable') }}
  34. q-btn(
  35. icon='mdi-tab-plus'
  36. padding='sm sm'
  37. flat
  38. )
  39. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertTabset') }}
  40. q-btn(
  41. icon='mdi-chart-multiline'
  42. padding='sm sm'
  43. flat
  44. )
  45. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertDiagram') }}
  46. q-btn(
  47. icon='mdi-book-plus'
  48. padding='sm sm'
  49. flat
  50. )
  51. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertFootnote') }}
  52. q-btn(
  53. icon='mdi-cookie-plus'
  54. padding='sm sm'
  55. flat
  56. )
  57. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertEmoji') }}
  58. q-btn(
  59. icon='mdi-line-scan'
  60. padding='sm sm'
  61. flat
  62. @click='insertHorizontalBar'
  63. )
  64. q-tooltip(anchor='center right' self='center left') {{ t('editor.markup.insertHorizontalBar') }}
  65. q-space
  66. span.editor-markdown-type Markdown
  67. .editor-markdown-mid
  68. //--------------------------------------------------------
  69. //- TOP TOOLBAR
  70. //--------------------------------------------------------
  71. .editor-markdown-toolbar
  72. q-btn(
  73. icon='mdi-format-bold'
  74. padding='xs sm'
  75. flat
  76. @click='toggleMarkup({ start: `**` })'
  77. )
  78. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.bold') }}
  79. q-btn(
  80. icon='mdi-format-italic'
  81. padding='xs sm'
  82. flat
  83. @click='toggleMarkup({ start: `*` })'
  84. )
  85. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.italic') }}
  86. q-btn(
  87. icon='mdi-format-strikethrough'
  88. padding='xs sm'
  89. flat
  90. @click='toggleMarkup({ start: `~~` })'
  91. )
  92. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.strikethrough') }}
  93. q-btn(
  94. icon='mdi-format-header-pound'
  95. padding='xs sm'
  96. flat
  97. )
  98. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.header') }}
  99. q-menu(auto-close)
  100. q-list(separator)
  101. q-item(
  102. v-for='lvl in 6'
  103. clickable
  104. @click='setHeaderLine(lvl)'
  105. )
  106. q-item-section(side)
  107. q-icon(:name='`mdi-format-header-` + lvl')
  108. q-item-section {{ t('editor.markup.headerLevel', { level: lvl }) }}
  109. q-btn(
  110. icon='mdi-format-subscript'
  111. padding='xs sm'
  112. flat
  113. @click='toggleMarkup({ start: `~` })'
  114. )
  115. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.subscript') }}
  116. q-btn(
  117. icon='mdi-format-superscript'
  118. padding='xs sm'
  119. flat
  120. @click='toggleMarkup({ start: `^` })'
  121. )
  122. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.superscript') }}
  123. q-btn(
  124. icon='mdi-alpha-t-box-outline'
  125. padding='xs sm'
  126. flat
  127. )
  128. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.blockquoteAdmonitions') }}
  129. q-menu(auto-close)
  130. q-list(separator)
  131. q-item(clickable, @click='insertBeforeEachLine({ content: `> `})')
  132. q-item-section(side)
  133. q-icon(name='mdi-format-quote-close')
  134. q-item-section {{ t('editor.markup.blockquote') }}
  135. q-item(clickable, @click='insertBeforeEachLine({ content: `> `, after: `{.is-info}`})')
  136. q-item-section(side)
  137. q-icon(name='mdi-information-box', color='blue-7')
  138. q-item-section {{ t('editor.markup.admonitionInfo') }}
  139. q-item(clickable, @click='insertBeforeEachLine({ content: `> `, after: `{.is-success}`})')
  140. q-item-section(side)
  141. q-icon(name='mdi-check-circle', color='positive')
  142. q-item-section {{ t('editor.markup.admonitionSuccess') }}
  143. q-item(clickable, @click='insertBeforeEachLine({ content: `> `, after: `{.is-warning}`})')
  144. q-item-section(side)
  145. q-icon(name='mdi-alert-box', color='orange')
  146. q-item-section {{ t('editor.markup.admonitionWarning') }}
  147. q-item(clickable, @click='insertBeforeEachLine({ content: `> `, after: `{.is-danger}`})')
  148. q-item-section(side)
  149. q-icon(name='mdi-close-box', color='negative')
  150. q-item-section {{ t('editor.markup.admonitionDanger') }}
  151. q-btn(
  152. icon='mdi-format-list-bulleted'
  153. padding='xs sm'
  154. flat
  155. @click='insertBeforeEachLine({ content: `- `})'
  156. )
  157. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.unorderedList') }}
  158. q-btn(
  159. icon='mdi-format-list-numbered'
  160. padding='xs sm'
  161. flat
  162. @click='insertBeforeEachLine({ content: `1. `})'
  163. )
  164. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.orderedList') }}
  165. q-btn(
  166. icon='mdi-format-list-checks'
  167. padding='xs sm'
  168. flat
  169. )
  170. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.taskList') }}
  171. q-menu(auto-close)
  172. q-list(separator)
  173. q-item(clickable, @click='insertBeforeEachLine({ content: `- [ ] `})')
  174. q-item-section(side)
  175. q-icon(name='mdi-checkbox-blank-outline')
  176. q-item-section {{ t('editor.markup.taskListUnchecked') }}
  177. q-item(clickable, @click='insertBeforeEachLine({ content: `- [x] `})')
  178. q-item-section(side)
  179. q-icon(name='mdi-checkbox-outline')
  180. q-item-section {{ t('editor.markup.taskListChecked') }}
  181. q-btn(
  182. icon='mdi-code-tags'
  183. padding='xs sm'
  184. flat
  185. @click='toggleMarkup({ start: "`" })'
  186. )
  187. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.inlineCode') }}
  188. q-btn(
  189. icon='mdi-keyboard-variant'
  190. padding='xs sm'
  191. flat
  192. @click='toggleMarkup({ start: `<kbd>`, end: `</kbd>` })'
  193. )
  194. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.markup.keyboardKey') }}
  195. q-btn(
  196. v-if='!state.previewShown'
  197. icon='mdi-eye-arrow-right-outline'
  198. padding='xs sm'
  199. flat
  200. @click='state.previewShown = true'
  201. )
  202. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.togglePreviewPane') }}
  203. //--------------------------------------------------------
  204. //- CODEMIRROR
  205. //--------------------------------------------------------
  206. .editor-markdown-editor
  207. textarea(ref='cmRef')
  208. transition(name='editor-markdown-preview')
  209. .editor-markdown-preview(v-if='state.previewShown')
  210. .editor-markdown-preview-toolbar
  211. strong: em {{ t('editor.renderPreview') }}
  212. q-separator.q-ml-md.q-mr-sm(vertical, inset)
  213. q-btn(
  214. icon='mdi-arrow-vertical-lock'
  215. padding='xs sm'
  216. flat
  217. @click='state.previewScrollSync = !state.previewScrollSync'
  218. :color='state.previewScrollSync ? `primary` : null'
  219. )
  220. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.toggleScrollSync') }}
  221. q-btn(
  222. icon='mdi-eye-off-outline'
  223. padding='xs sm'
  224. flat
  225. @click='state.previewShown = false'
  226. )
  227. q-tooltip(anchor='top middle' self='bottom middle') {{ t('editor.togglePreviewPane') }}
  228. .editor-markdown-preview-content.contents(ref='editorPreviewContainer')
  229. div(
  230. ref='editorPreview'
  231. v-html='state.previewHTML'
  232. )
  233. </template>
  234. <script setup>
  235. import { reactive, ref, shallowRef, nextTick, onBeforeMount, onMounted, watch } from 'vue'
  236. import { useMeta, useQuasar, setCssVar } from 'quasar'
  237. import { useI18n } from 'vue-i18n'
  238. import { get, flatten, last, times, startsWith, debounce } from 'lodash-es'
  239. import { useEditorStore } from 'src/stores/editor'
  240. import { usePageStore } from 'src/stores/page'
  241. import { useSiteStore } from 'src/stores/site'
  242. // Code Mirror
  243. import CodeMirror from 'codemirror'
  244. import 'codemirror/lib/codemirror.css'
  245. import '../css/codemirror.scss'
  246. // Language
  247. import 'codemirror/mode/markdown/markdown.js'
  248. // Addons
  249. import 'codemirror/addon/selection/active-line.js'
  250. import 'codemirror/addon/display/fullscreen.js'
  251. import 'codemirror/addon/display/fullscreen.css'
  252. import 'codemirror/addon/selection/mark-selection.js'
  253. import 'codemirror/addon/search/searchcursor.js'
  254. import 'codemirror/addon/hint/show-hint.js'
  255. import 'codemirror/addon/fold/foldcode.js'
  256. import 'codemirror/addon/fold/foldgutter.js'
  257. import 'codemirror/addon/fold/foldgutter.css'
  258. // Markdown Renderer
  259. import { MarkdownRenderer } from 'src/renderers/markdown'
  260. // QUASAR
  261. const $q = useQuasar()
  262. // STORES
  263. const editorStore = useEditorStore()
  264. const pageStore = usePageStore()
  265. const siteStore = useSiteStore()
  266. // I18N
  267. const { t } = useI18n()
  268. // STATE
  269. const cm = shallowRef(null)
  270. const cmRef = ref(null)
  271. const state = reactive({
  272. previewShown: true,
  273. previewHTML: '',
  274. previewScrollSync: true
  275. })
  276. const md = new MarkdownRenderer({})
  277. // Platform detection
  278. const CtrlKey = /Mac/.test(navigator.platform) ? 'Cmd' : 'Ctrl'
  279. // METHODS
  280. function insertAssets () {
  281. siteStore.$patch({
  282. overlay: 'FileManager'
  283. })
  284. }
  285. function insertTable () {
  286. siteStore.$patch({
  287. overlay: 'TableEditor'
  288. })
  289. }
  290. /**
  291. * Set current line as header
  292. */
  293. function setHeaderLine (lvl) {
  294. const curLine = cm.value.doc.getCursor('head').line
  295. let lineContent = cm.value.doc.getLine(curLine)
  296. const lineLength = lineContent.length
  297. if (startsWith(lineContent, '#')) {
  298. lineContent = lineContent.replace(/^(#+ )/, '')
  299. }
  300. lineContent = times(lvl, n => '#').join('') + ' ' + lineContent
  301. cm.value.doc.replaceRange(lineContent, { line: curLine, ch: 0 }, { line: curLine, ch: lineLength })
  302. }
  303. /**
  304. * Get the header lever of the current line
  305. */
  306. function getHeaderLevel (cm) {
  307. const curLine = cm.doc.getCursor('head').line
  308. const lineContent = cm.doc.getLine(curLine)
  309. let lvl = 0
  310. const result = lineContent.match(/^(#+) /)
  311. if (result) {
  312. lvl = get(result, '[1]', '').length
  313. }
  314. return lvl
  315. }
  316. /**
  317. * Insert content at cursor
  318. */
  319. function insertAtCursor ({ content }) {
  320. const cursor = cm.value.doc.getCursor('head')
  321. cm.value.doc.replaceRange(content, cursor)
  322. }
  323. /**
  324. * Insert content after current line
  325. */
  326. function insertAfter ({ content, newLine }) {
  327. const curLine = cm.value.doc.getCursor('to').line
  328. const lineLength = cm.value.doc.getLine(curLine).length
  329. cm.value.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 })
  330. }
  331. /**
  332. * Insert content before current line
  333. */
  334. function insertBeforeEachLine ({ content, after }) {
  335. let lines = []
  336. if (!cm.value.doc.somethingSelected()) {
  337. lines.push(cm.value.doc.getCursor('head').line)
  338. } else {
  339. lines = flatten(cm.value.doc.listSelections().map(sl => {
  340. const range = Math.abs(sl.anchor.line - sl.head.line) + 1
  341. const lowestLine = (sl.anchor.line > sl.head.line) ? sl.head.line : sl.anchor.line
  342. return times(range, l => l + lowestLine)
  343. }))
  344. }
  345. lines.forEach(ln => {
  346. let lineContent = cm.value.doc.getLine(ln)
  347. const lineLength = lineContent.length
  348. if (startsWith(lineContent, content)) {
  349. lineContent = lineContent.substring(content.length)
  350. }
  351. cm.value.doc.replaceRange(content + lineContent, { line: ln, ch: 0 }, { line: ln, ch: lineLength })
  352. })
  353. if (after) {
  354. const lastLine = last(lines)
  355. cm.value.doc.replaceRange(`\n${after}\n`, { line: lastLine, ch: cm.value.doc.getLine(lastLine).length + 1 })
  356. }
  357. }
  358. /**
  359. * Insert an Horizontal Bar
  360. */
  361. function insertHorizontalBar () {
  362. insertAfter({ content: '---', newLine: true })
  363. }
  364. /**
  365. * Toggle Markup at selection
  366. */
  367. function toggleMarkup ({ start, end }) {
  368. if (!end) { end = start }
  369. if (!cm.value.doc.somethingSelected()) {
  370. return $q.notify({
  371. type: 'negative',
  372. message: t('editor.markup.noSelectionError')
  373. })
  374. }
  375. cm.value.doc.replaceSelections(cm.value.doc.getSelections().map(s => start + s + end))
  376. }
  377. const onCmInput = debounce(processContent, 600)
  378. function processContent (newContent) {
  379. state.previewHTML = md.render(newContent)
  380. }
  381. // MOUNTED
  382. onMounted(async () => {
  383. // -> Setup Editor View
  384. editorStore.$patch({
  385. hideSideNav: true
  386. })
  387. // -> Initialize CodeMirror
  388. cm.value = CodeMirror.fromTextArea(cmRef.value, {
  389. tabSize: 2,
  390. mode: 'text/markdown',
  391. theme: 'wikijs-dark',
  392. lineNumbers: true,
  393. lineWrapping: true,
  394. line: true,
  395. styleActiveLine: true,
  396. highlightSelectionMatches: {
  397. annotateScrollbar: true
  398. },
  399. viewportMargin: 50,
  400. inputStyle: 'contenteditable',
  401. allowDropFileTypes: ['image/jpg', 'image/png', 'image/svg', 'image/jpeg', 'image/gif'],
  402. // direction: siteConfig.rtl ? 'rtl' : 'ltr',
  403. foldGutter: true,
  404. gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
  405. })
  406. cm.value.setValue(pageStore.content)
  407. cm.value.on('change', c => {
  408. pageStore.$patch({
  409. content: c.getValue()
  410. })
  411. onCmInput(pageStore.content)
  412. })
  413. cm.value.setSize(null, '100%')
  414. // -> Set Keybindings
  415. const keyBindings = {
  416. 'F11' (c) {
  417. c.setOption('fullScreen', !c.getOption('fullScreen'))
  418. },
  419. 'Esc' (c) {
  420. if (c.getOption('fullScreen')) {
  421. c.setOption('fullScreen', false)
  422. }
  423. },
  424. [`${CtrlKey}-S`] (c) {
  425. // save()
  426. return false
  427. },
  428. [`${CtrlKey}-B`] (c) {
  429. toggleMarkup({ start: '**' })
  430. return false
  431. },
  432. [`${CtrlKey}-I`] (c) {
  433. toggleMarkup({ start: '*' })
  434. return false
  435. },
  436. [`${CtrlKey}-Alt-Right`] (c) {
  437. let lvl = getHeaderLevel(c)
  438. if (lvl >= 6) { lvl = 5 }
  439. setHeaderLine(lvl + 1)
  440. return false
  441. },
  442. [`${CtrlKey}-Alt-Left`] (c) {
  443. let lvl = getHeaderLevel(c)
  444. if (lvl <= 1) { lvl = 2 }
  445. setHeaderLine(lvl - 1)
  446. return false
  447. }
  448. }
  449. cm.value.setOption('extraKeys', keyBindings)
  450. // this.cm.on('inputRead', this.autocomplete)
  451. // // Handle cursor movement
  452. // this.cm.on('cursorActivity', c => {
  453. // this.positionSync(c)
  454. // this.scrollSync(c)
  455. // })
  456. // // Handle special paste
  457. // this.cm.on('paste', this.onCmPaste)
  458. // // Render initial preview
  459. // this.processContent(this.$store.get('editor/content'))
  460. nextTick(() => {
  461. cm.value.refresh()
  462. cm.value.focus()
  463. })
  464. // this.$root.$on('editorInsert', opts => {
  465. // switch (opts.kind) {
  466. // case 'IMAGE':
  467. // let img = `![${opts.text}](${opts.path})`
  468. // if (opts.align && opts.align !== '') {
  469. // img += `{.align-${opts.align}}`
  470. // }
  471. // this.insertAtCursor({
  472. // content: img
  473. // })
  474. // break
  475. // case 'BINARY':
  476. // this.insertAtCursor({
  477. // content: `[${opts.text}](${opts.path})`
  478. // })
  479. // break
  480. // case 'DIAGRAM':
  481. // const selStartLine = this.cm.getCursor('from').line
  482. // const selEndLine = this.cm.getCursor('to').line + 1
  483. // this.cm.doc.replaceSelection('```diagram\n' + opts.text + '\n```\n', 'start')
  484. // this.processMarkers(selStartLine, selEndLine)
  485. // break
  486. // }
  487. // })
  488. // // Handle save conflict
  489. // this.$root.$on('saveConflict', () => {
  490. // this.toggleModal(`editorModalConflict`)
  491. // })
  492. // this.$root.$on('overwriteEditorContent', () => {
  493. // this.cm.setValue(this.$store.get('editor/content'))
  494. // })
  495. })
  496. onBeforeMount(() => {
  497. // if (editor.value) {
  498. // editor.value.destroy()
  499. // }
  500. })
  501. </script>
  502. <style lang="scss">
  503. $editor-height: calc(100vh - 64px - 94px - 2px);
  504. $editor-height-mobile: calc(100vh - 112px - 16px);
  505. .editor-markdown {
  506. &-main {
  507. display: flex;
  508. width: 100%;
  509. }
  510. &-mid {
  511. background-color: $dark-6;
  512. flex: 1 1 50%;
  513. display: block;
  514. height: $editor-height;
  515. position: relative;
  516. border-right: 5px solid $primary;
  517. }
  518. &-editor {
  519. display: block;
  520. height: calc(100% - 32px);
  521. position: relative;
  522. // @include until($tablet) {
  523. // height: $editor-height-mobile;
  524. // }
  525. }
  526. &-type {
  527. writing-mode: vertical-rl;
  528. text-orientation: mixed;
  529. padding-bottom: 1rem;
  530. color: rgba(255,255,255, .4);
  531. font-weight: 500;
  532. }
  533. &-preview {
  534. flex: 1 1 50%;
  535. position: relative;
  536. height: $editor-height;
  537. overflow: hidden;
  538. @at-root .body--light & {
  539. background-color: $grey-2;
  540. }
  541. @at-root .body--dark & {
  542. background-color: $dark-4;
  543. }
  544. // @include until($tablet) {
  545. // display: none;
  546. // }
  547. &-enter-active, &-leave-active {
  548. transition: max-width .5s ease;
  549. max-width: 50vw;
  550. .editor-code-preview-content {
  551. width: 50vw;
  552. overflow:hidden;
  553. }
  554. }
  555. &-enter, &-leave-to {
  556. max-width: 0;
  557. }
  558. &-toolbar {
  559. color: $grey-8;
  560. height: 32px;
  561. display: flex;
  562. align-items: center;
  563. padding: 0 1rem;
  564. @at-root .body--light & {
  565. background-color: $grey-3;
  566. }
  567. @at-root .body--dark & {
  568. background-color: $dark-2;
  569. color: $grey-6;
  570. }
  571. }
  572. &-content {
  573. height: $editor-height;
  574. overflow-y: scroll;
  575. padding: 1rem;
  576. width: calc(100% + 17px);
  577. // -ms-overflow-style: none;
  578. // &::-webkit-scrollbar {
  579. // width: 0px;
  580. // background: transparent;
  581. // }
  582. // @include until($tablet) {
  583. // height: $editor-height-mobile;
  584. // }
  585. > div {
  586. outline: none;
  587. }
  588. p.line {
  589. overflow-wrap: break-word;
  590. }
  591. .tabset {
  592. background-color: $teal-7;
  593. color: $teal-2 !important;
  594. padding: 5px 12px;
  595. font-size: 14px;
  596. font-weight: 500;
  597. border-radius: 5px 0 0 0;
  598. font-style: italic;
  599. &::after {
  600. display: none;
  601. }
  602. &-header {
  603. background-color: $teal-5;
  604. color: #FFF !important;
  605. padding: 5px 12px;
  606. font-size: 14px;
  607. font-weight: 500;
  608. margin-top: 0 !important;
  609. &::after {
  610. display: none;
  611. }
  612. }
  613. &-content {
  614. border-left: 5px solid $teal-5;
  615. background-color: $teal-1;
  616. padding: 0 15px 15px;
  617. overflow: hidden;
  618. @at-root .theme--dark & {
  619. background-color: rgba($teal-5, .1);
  620. }
  621. }
  622. }
  623. }
  624. }
  625. &-toolbar {
  626. background-color: $primary;
  627. border-left: 40px solid darken($primary, 5%);
  628. color: #FFF;
  629. height: 32px;
  630. }
  631. &-sidebar {
  632. background-color: $dark-4;
  633. border-top: 32px solid darken($primary, 10%);
  634. color: #FFF;
  635. width: 56px;
  636. display: flex;
  637. flex-direction: column;
  638. justify-content: flex-start;
  639. align-items: center;
  640. padding: 12px 0;
  641. }
  642. &-sysbar {
  643. padding-left: 0;
  644. &-locale {
  645. background-color: rgba(255,255,255,.25);
  646. display:inline-flex;
  647. padding: 0 12px;
  648. height: 24px;
  649. width: 63px;
  650. justify-content: center;
  651. align-items: center;
  652. }
  653. }
  654. // ==========================================
  655. // CODE MIRROR
  656. // ==========================================
  657. .CodeMirror {
  658. height: auto;
  659. font-family: 'Roboto Mono', monospace;
  660. font-size: .9rem;
  661. .cm-header-1 {
  662. font-size: 1.5rem;
  663. }
  664. .cm-header-2 {
  665. font-size: 1.25rem;
  666. }
  667. .cm-header-3 {
  668. font-size: 1.15rem;
  669. }
  670. .cm-header-4 {
  671. font-size: 1.1rem;
  672. }
  673. .cm-header-5 {
  674. font-size: 1.05rem;
  675. }
  676. .cm-header-6 {
  677. font-size: 1.025rem;
  678. }
  679. }
  680. .CodeMirror-wrap pre.CodeMirror-line, .CodeMirror-wrap pre.CodeMirror-line-like {
  681. word-break: break-word;
  682. }
  683. .CodeMirror-focused .cm-matchhighlight {
  684. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
  685. background-position: bottom;
  686. background-repeat: repeat-x;
  687. }
  688. .cm-matchhighlight {
  689. background-color: $grey-8;
  690. }
  691. .CodeMirror-selection-highlight-scrollbar {
  692. background-color: $green-6;
  693. }
  694. }
  695. // HINT DROPDOWN
  696. .CodeMirror-hints {
  697. position: absolute;
  698. z-index: 10;
  699. overflow: hidden;
  700. list-style: none;
  701. margin: 0;
  702. padding: 1px;
  703. box-shadow: 2px 3px 5px rgba(0,0,0,.2);
  704. border: 1px solid $grey-7;
  705. background: $grey-9;
  706. font-family: 'Roboto Mono', monospace;
  707. font-size: .9rem;
  708. max-height: 150px;
  709. overflow-y: auto;
  710. min-width: 250px;
  711. max-width: 80vw;
  712. }
  713. .CodeMirror-hint {
  714. margin: 0;
  715. padding: 0 4px;
  716. white-space: pre;
  717. color: #FFF;
  718. cursor: pointer;
  719. }
  720. li.CodeMirror-hint-active {
  721. background: $blue-5;
  722. color: #FFF;
  723. }
  724. </style>