editor-asciidoc.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. <template lang='pug'>
  2. .editor-asciidoc
  3. v-toolbar.editor-asciidoc-toolbar(dense, color='primary', dark, flat, style='overflow-x: hidden;')
  4. template(v-if='isModalShown')
  5. v-spacer
  6. v-btn.animated.fadeInRight(text, @click='closeAllModal')
  7. v-icon(left) mdi-arrow-left-circle
  8. span {{$t('editor:backToEditor')}}
  9. template(v-else)
  10. v-tooltip(bottom, color='primary')
  11. template(v-slot:activator='{ on }')
  12. v-btn.animated.fadeIn(icon, tile, v-on='on', @click='toggleMarkup({ start: `**` })').mx-0
  13. v-icon mdi-format-bold
  14. span {{$t('editor:markup.bold')}}
  15. v-tooltip(bottom, color='primary')
  16. template(v-slot:activator='{ on }')
  17. v-btn.animated.fadeIn.wait-p1s(icon, tile, v-on='on', @click='toggleMarkup({ start: `__` })').mx-0
  18. v-icon mdi-format-italic
  19. span {{$t('editor:markup.italic')}}
  20. v-menu(offset-y, open-on-hover)
  21. template(v-slot:activator='{ on }')
  22. v-btn.animated.fadeIn.wait-p3s(icon, tile, v-on='on').mx-0
  23. v-icon mdi-format-header-pound
  24. v-list.py-0
  25. template(v-for='(n, idx) in 6')
  26. v-list-item(@click='setHeaderLine(n)', :key='idx')
  27. v-list-item-action
  28. v-icon(:size='24 - (idx - 1) * 2') mdi-format-header-{{n}}
  29. v-list-item-title {{$t('editor:markup.heading', { level: n })}}
  30. v-divider(v-if='idx < 5')
  31. v-tooltip(bottom, color='primary')
  32. template(v-slot:activator='{ on }')
  33. v-btn.animated.fadeIn.wait-p4s(icon, tile, v-on='on', @click='toggleMarkup({ start: `~` })').mx-0
  34. v-icon mdi-format-subscript
  35. span {{$t('editor:markup.subscript')}}
  36. v-tooltip(bottom, color='primary')
  37. template(v-slot:activator='{ on }')
  38. v-btn.animated.fadeIn.wait-p5s(icon, tile, v-on='on', @click='toggleMarkup({ start: `^` })').mx-0
  39. v-icon mdi-format-superscript
  40. span {{$t('editor:markup.superscript')}}
  41. v-menu(offset-y, open-on-hover)
  42. template(v-slot:activator='{ on }')
  43. v-btn.animated.fadeIn.wait-p6s(icon, tile, v-on='on').mx-0
  44. v-icon mdi-alpha-t-box-outline
  45. v-list.py-0
  46. v-list-item(@click='insertBeforeEachLine({ content: `> `})')
  47. v-list-item-action
  48. v-icon mdi-alpha-t-box-outline
  49. v-list-item-title {{$t('editor:markup.blockquote')}}
  50. v-divider
  51. v-list-item(@click='insertBeforeEachLine({ content: `NOTE: `})')
  52. v-list-item-action
  53. v-icon(color='blue') mdi-alpha-n-box-outline
  54. v-list-item-title {{'Note blockquote'}}
  55. v-divider
  56. v-list-item(@click='insertBeforeEachLine({ content: `TIP: `})')
  57. v-list-item-action
  58. v-icon(color='success') mdi-alpha-t-box-outline
  59. v-list-item-title {{'Tip blockquote'}}
  60. v-divider
  61. v-list-item(@click='insertBeforeEachLine({ content: `WARNING: `})')
  62. v-list-item-action
  63. v-icon(color='warning') mdi-alpha-w-box-outline
  64. v-list-item-title {{$t('editor:markup.blockquoteWarning')}}
  65. v-divider
  66. v-list-item(@click='insertBeforeEachLine({ content: `CAUTION: `})')
  67. v-list-item-action
  68. v-icon(color='purple') mdi-alpha-c-box-outline
  69. v-list-item-title {{'Caution blockquote'}}
  70. v-list-item(@click='insertBeforeEachLine({ content: `IMPORTANT: `})')
  71. v-list-item-action
  72. v-icon(color='error') mdi-alpha-i-box-outline
  73. v-list-item-title {{'Important blockquote'}}
  74. v-divider
  75. template(v-if='$vuetify.breakpoint.mdAndUp')
  76. v-spacer
  77. v-tooltip(bottom, color='primary')
  78. template(v-slot:activator='{ on }')
  79. v-btn.animated.fadeIn.wait-p2s(icon, tile, v-on='on', @click='previewShown = !previewShown').mx-0
  80. v-icon mdi-book-open-outline
  81. span {{$t('editor:markup.togglePreviewPane')}}
  82. .editor-asciidoc-main
  83. .editor-asciidoc-sidebar
  84. v-tooltip(right, color='teal')
  85. template(v-slot:activator='{ on }')
  86. v-btn.animated.fadeInLeft(icon, tile, v-on='on', dark, @click='insertLink').mx-0
  87. v-icon mdi-link-plus
  88. span {{$t('editor:markup.insertLink')}}
  89. v-tooltip(right, color='teal')
  90. template(v-slot:activator='{ on }')
  91. v-btn.mt-3.animated.fadeInLeft.wait-p1s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalMedia`)').mx-0
  92. v-icon(:color='activeModal === `editorModalMedia` ? `teal` : ``') mdi-folder-multiple-image
  93. span {{$t('editor:markup.insertAssets')}}
  94. v-tooltip(right, color='teal')
  95. template(v-slot:activator='{ on }')
  96. v-btn.mt-3.animated.fadeInLeft.wait-p5s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalDrawio`)').mx-0
  97. v-icon mdi-chart-multiline
  98. span {{$t('editor:markup.insertDiagram')}}
  99. template(v-if='$vuetify.breakpoint.mdAndUp')
  100. v-spacer
  101. v-tooltip(right, color='teal')
  102. template(v-slot:activator='{ on }')
  103. v-btn.mt-3.animated.fadeInLeft.wait-p8s(icon, tile, v-on='on', dark, @click='toggleFullscreen').mx-0
  104. v-icon mdi-arrow-expand-all
  105. span {{$t('editor:markup.distractionFreeMode')}}
  106. .editor-asciidoc-editor
  107. textarea(ref='cm')
  108. transition(name='editor-asciidoc-preview')
  109. .editor-asciidoc-preview(v-if='previewShown')
  110. .editor-asciidoc-preview-content.contents(ref='editorPreviewContainer')
  111. div(
  112. ref='editorPreview'
  113. v-html='previewHTML'
  114. )
  115. v-system-bar.editor-asciidoc-sysbar(dark, status, color='grey darken-3')
  116. .caption.editor-asciidoc-sysbar-locale {{locale.toUpperCase()}}
  117. .caption.px-3 /{{path}}
  118. template(v-if='$vuetify.breakpoint.mdAndUp')
  119. v-spacer
  120. .caption AsciiDoc
  121. v-spacer
  122. .caption Ln {{cursorPos.line + 1}}, Col {{cursorPos.ch + 1}}
  123. page-selector(mode='select', v-model='insertLinkDialog', :open-handler='insertLinkHandler', :path='path', :locale='locale')
  124. </template>
  125. <script>
  126. import _ from 'lodash'
  127. import { get, sync } from 'vuex-pathify'
  128. import DOMPurify from 'dompurify'
  129. // ========================================
  130. // IMPORTS
  131. // ========================================
  132. // Code Mirror
  133. import CodeMirror from 'codemirror'
  134. import 'codemirror/lib/codemirror.css'
  135. // Language
  136. import 'codemirror-asciidoc'
  137. // Addons
  138. import 'codemirror/addon/selection/active-line.js'
  139. import 'codemirror/addon/display/fullscreen.js'
  140. import 'codemirror/addon/display/fullscreen.css'
  141. import 'codemirror/addon/selection/mark-selection.js'
  142. import 'codemirror/addon/search/searchcursor.js'
  143. import 'codemirror/addon/hint/show-hint.js'
  144. import 'codemirror/addon/fold/foldcode.js'
  145. import 'codemirror/addon/fold/foldgutter.js'
  146. import 'codemirror/addon/fold/foldgutter.css'
  147. import cmFold from './common/cmFold'
  148. // ========================================
  149. // INIT
  150. // ========================================
  151. const asciidoctor = require('asciidoctor')()
  152. const cheerio = require('cheerio')
  153. // Platform detection
  154. const CtrlKey = /Mac/.test(navigator.platform) ? 'Cmd' : 'Ctrl'
  155. // ========================================
  156. // HELPER FUNCTIONS
  157. // ========================================
  158. cmFold.register('asciidoc')
  159. // ========================================
  160. // Vue Component
  161. // ========================================
  162. export default {
  163. data() {
  164. return {
  165. cm: null,
  166. cursorPos: { ch: 0, line: 1 },
  167. previewShown: true, // TODO
  168. insertLinkDialog: false,
  169. helpShown: false,
  170. previewHTML: ''
  171. }
  172. },
  173. computed: {
  174. isMobile() {
  175. return this.$vuetify.breakpoint.smAndDown
  176. },
  177. isModalShown() {
  178. return this.helpShown || this.activeModal !== ''
  179. },
  180. locale: get('page/locale'),
  181. path: get('page/path'),
  182. mode: get('editor/mode'),
  183. activeModal: sync('editor/activeModal')
  184. },
  185. methods: {
  186. toggleModal(key) {
  187. this.activeModal = (this.activeModal === key) ? '' : key
  188. this.helpShown = false
  189. },
  190. closeAllModal() {
  191. this.activeModal = ''
  192. this.helpShown = false
  193. },
  194. onCmInput: _.debounce(function(newContent) {
  195. this.processContent(newContent)
  196. }, 600),
  197. processContent(newContent) {
  198. this.processMarkers(this.cm.firstLine(), this.cm.lastLine())
  199. let html = asciidoctor.convert(newContent, {
  200. standalone: false,
  201. safe: 'safe',
  202. attributes: {
  203. showtitle: true,
  204. icons: 'font'
  205. }
  206. })
  207. const $ = cheerio.load(html, {
  208. decodeEntities: true
  209. })
  210. $('pre.highlight > code.language-diagram').each((i, elm) => {
  211. const diagramContent = Buffer.from($(elm).html(), 'base64').toString()
  212. $(elm).parent().replaceWith(`<pre class="diagram">${diagramContent}</div>`)
  213. })
  214. this.previewHTML = DOMPurify.sanitize($.html(), {
  215. ADD_TAGS: ['foreignObject']
  216. })
  217. },
  218. /**
  219. * Insert content at cursor
  220. */
  221. insertAtCursor({ content }) {
  222. const cursor = this.cm.doc.getCursor('head')
  223. this.cm.doc.replaceRange(content, cursor)
  224. },
  225. /**
  226. * Insert content after current line
  227. */
  228. insertAfter({ content, newLine }) {
  229. const curLine = this.cm.doc.getCursor('to').line
  230. const lineLength = this.cm.doc.getLine(curLine).length
  231. this.cm.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 })
  232. },
  233. /**
  234. * Insert content before current line
  235. */
  236. insertBeforeEachLine({ content, after }) {
  237. let lines = []
  238. if (!this.cm.doc.somethingSelected()) {
  239. lines.push(this.cm.doc.getCursor('head').line)
  240. } else {
  241. lines = _.flatten(this.cm.doc.listSelections().map(sl => {
  242. const range = Math.abs(sl.anchor.line - sl.head.line) + 1
  243. const lowestLine = (sl.anchor.line > sl.head.line) ? sl.head.line : sl.anchor.line
  244. return _.times(range, l => l + lowestLine)
  245. }))
  246. }
  247. lines.forEach(ln => {
  248. let lineContent = this.cm.doc.getLine(ln)
  249. const lineLength = lineContent.length
  250. if (_.startsWith(lineContent, content)) {
  251. lineContent = lineContent.substring(content.length)
  252. }
  253. this.cm.doc.replaceRange(content + lineContent, { line: ln, ch: 0 }, { line: ln, ch: lineLength })
  254. })
  255. if (after) {
  256. const lastLine = _.last(lines)
  257. this.cm.doc.replaceRange(`\n${after}\n`, { line: lastLine, ch: this.cm.doc.getLine(lastLine).length + 1 })
  258. }
  259. },
  260. /**
  261. * Update cursor state
  262. */
  263. positionSync(cm) {
  264. this.cursorPos = cm.getCursor('head')
  265. },
  266. toggleMarkup({ start, end }) {
  267. if (!end) { end = start }
  268. if (!this.cm.doc.somethingSelected()) {
  269. return this.$store.commit('showNotification', {
  270. message: this.$t('editor:markup.noSelectionError'),
  271. style: 'warning',
  272. icon: 'warning'
  273. })
  274. }
  275. this.cm.doc.replaceSelections(this.cm.doc.getSelections().map(s => start + s + end))
  276. },
  277. setHeaderLine(lvl) {
  278. const curLine = this.cm.doc.getCursor('head').line
  279. let lineContent = this.cm.doc.getLine(curLine)
  280. const lineLength = lineContent.length
  281. if (_.startsWith(lineContent, '=')) {
  282. lineContent = lineContent.replace(/^(=+ )/, '')
  283. }
  284. lineContent = _.times(lvl, n => '=').join('') + ` ` + lineContent
  285. this.cm.doc.replaceRange(lineContent, { line: curLine, ch: 0 }, { line: curLine, ch: lineLength })
  286. },
  287. toggleFullscreen () {
  288. this.cm.setOption('fullScreen', true)
  289. },
  290. refresh() {
  291. this.$nextTick(() => {
  292. this.cm.refresh()
  293. })
  294. },
  295. insertLink () {
  296. this.insertLinkDialog = true
  297. },
  298. insertLinkHandler ({ locale, path }) {
  299. const lastPart = _.last(path.split('/'))
  300. this.insertAtCursor({
  301. content: siteLangs.length > 0 ? `link:/${locale}/${path}[${lastPart}]` : `link:/${path}[${lastPart}]`
  302. })
  303. },
  304. processMarkers (from, to) {
  305. let found = null
  306. let foundStart = 0
  307. this.cm.doc.getAllMarks().forEach(mk => {
  308. if (mk.__kind) {
  309. mk.clear()
  310. }
  311. })
  312. this.cm.eachLine(from, to, ln => {
  313. const line = ln.lineNo()
  314. if (ln.text.startsWith('```diagram')) {
  315. found = 'diagram'
  316. foundStart = line
  317. } else if (ln.text === '```' && found) {
  318. switch (found) {
  319. // ------------------------------
  320. // -> DIAGRAM
  321. // ------------------------------
  322. case 'diagram': {
  323. if (line - foundStart !== 2) {
  324. return
  325. }
  326. this.addMarker({
  327. kind: 'diagram',
  328. from: { line: foundStart, ch: 3 },
  329. to: { line: foundStart, ch: 10 },
  330. text: 'Edit Diagram',
  331. action: ((start, end) => {
  332. return (ev) => {
  333. this.cm.doc.setSelection({ line: start, ch: 0 }, { line: end, ch: 3 })
  334. try {
  335. const raw = this.cm.doc.getLine(end - 1)
  336. this.$store.set('editor/activeModalData', Buffer.from(raw, 'base64').toString())
  337. this.toggleModal(`editorModalDrawio`)
  338. } catch (err) {
  339. return this.$store.commit('showNotification', {
  340. message: 'Failed to process diagram data.',
  341. style: 'warning',
  342. icon: 'warning'
  343. })
  344. }
  345. }
  346. })(foundStart, line)
  347. })
  348. if (ln.height > 0) {
  349. this.cm.foldCode(foundStart)
  350. }
  351. break
  352. }
  353. }
  354. found = null
  355. }
  356. })
  357. },
  358. addMarker ({ kind, from, to, text, action }) {
  359. const markerElm = document.createElement('span')
  360. markerElm.appendChild(document.createTextNode(text))
  361. markerElm.className = 'CodeMirror-buttonmarker'
  362. markerElm.addEventListener('click', action)
  363. this.cm.markText(from, to, { replacedWith: markerElm, __kind: kind })
  364. }
  365. },
  366. mounted() {
  367. this.$store.set('editor/editorKey', 'asciidoc')
  368. if (this.mode === 'create') {
  369. this.$store.set('editor/content', '== header\n\ncontent')
  370. }
  371. // Initialize CodeMirror
  372. this.cm = CodeMirror.fromTextArea(this.$refs.cm, {
  373. tabSize: 2,
  374. mode: 'asciidoc',
  375. theme: 'wikijs-dark',
  376. lineNumbers: true,
  377. lineWrapping: true,
  378. line: true,
  379. styleActiveLine: true,
  380. highlightSelectionMatches: {
  381. annotateScrollbar: true
  382. },
  383. viewportMargin: 50,
  384. inputStyle: 'contenteditable',
  385. allowDropFileTypes: ['image/jpg', 'image/png', 'image/svg', 'image/jpeg', 'image/gif'],
  386. direction: siteConfig.rtl ? 'rtl' : 'ltr',
  387. foldGutter: true,
  388. gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter']
  389. })
  390. this.cm.setValue(this.$store.get('editor/content'))
  391. this.cm.on('change', c => {
  392. this.$store.set('editor/content', c.getValue())
  393. this.onCmInput(this.$store.get('editor/content'))
  394. })
  395. if (this.$vuetify.breakpoint.mdAndUp) {
  396. this.cm.setSize(null, 'calc(100vh - 137px)')
  397. } else {
  398. this.cm.setSize(null, 'calc(100vh - 112px - 16px)')
  399. }
  400. // Set Keybindings
  401. const keyBindings = {
  402. 'F11' (c) {
  403. c.setOption('fullScreen', !c.getOption('fullScreen'))
  404. },
  405. 'Esc' (c) {
  406. if (c.getOption('fullScreen')) c.setOption('fullScreen', false)
  407. }
  408. }
  409. _.set(keyBindings, `${CtrlKey}-B`, c => {
  410. this.toggleMarkup({ start: `**` })
  411. return false
  412. })
  413. _.set(keyBindings, `${CtrlKey}-I`, c => {
  414. this.toggleMarkup({ start: `__` })
  415. return false
  416. })
  417. this.cm.setOption('extraKeys', keyBindings)
  418. // Handle cursor movement
  419. this.cm.on('cursorActivity', c => {
  420. this.positionSync(c)
  421. })
  422. // Render initial preview
  423. this.processContent(this.$store.get('editor/content'))
  424. this.$root.$on('editorInsert', opts => {
  425. switch (opts.kind) {
  426. case 'IMAGE':
  427. let img = `image::${opts.path}[${opts.text}]`
  428. this.insertAtCursor({
  429. content: img
  430. })
  431. break
  432. case 'BINARY':
  433. this.insertAtCursor({
  434. content: `link:${opts.path}[${opts.text}]`
  435. })
  436. break
  437. case 'DIAGRAM':
  438. const selStartLine = this.cm.getCursor('from').line
  439. const selEndLine = this.cm.getCursor('to').line + 1
  440. this.cm.doc.replaceSelection('```diagram\n' + opts.text + '\n```\n', 'start')
  441. this.processMarkers(selStartLine, selEndLine)
  442. break
  443. }
  444. })
  445. // Handle save conflict
  446. this.$root.$on('saveConflict', () => {
  447. this.toggleModal(`editorModalConflict`)
  448. })
  449. this.$root.$on('overwriteEditorContent', () => {
  450. this.cm.setValue(this.$store.get('editor/content'))
  451. })
  452. },
  453. beforeDestroy() {
  454. this.$root.$off('editorInsert')
  455. }
  456. }
  457. </script>
  458. <style lang='scss'>
  459. $editor-ascii-height: calc(100vh - 137px);
  460. $editor-ascii-height-mobile: calc(100vh - 112px - 16px);
  461. .editor-asciidoc {
  462. &-main {
  463. display: flex;
  464. width: 100%;
  465. }
  466. &-editor {
  467. background-color: darken(mc('grey', '900'), 4.5%);
  468. flex: 1 1 50%;
  469. display: block;
  470. height: $editor-ascii-height;
  471. position: relative;
  472. @include until($tablet) {
  473. height: $editor-ascii-height-mobile;
  474. }
  475. }
  476. &-preview {
  477. flex: 1 1 50%;
  478. background-color: mc('grey', '100');
  479. position: relative;
  480. height: $editor-ascii-height;
  481. overflow: hidden;
  482. padding: 1rem;
  483. @at-root .theme--dark & {
  484. background-color: mc('grey', '900');
  485. }
  486. @include until($tablet) {
  487. display: none;
  488. }
  489. &-enter-active, &-leave-active {
  490. transition: max-width .5s ease;
  491. max-width: 50vw;
  492. .editor-code-preview-content {
  493. width: 50vw;
  494. overflow:hidden;
  495. }
  496. }
  497. &-enter, &-leave-to {
  498. max-width: 0;
  499. }
  500. &-content {
  501. height: $editor-ascii-height;
  502. overflow-y: scroll;
  503. padding: 0;
  504. width: calc(100% + 17px);
  505. // -ms-overflow-style: none;
  506. // &::-webkit-scrollbar {
  507. // width: 0px;
  508. // background: transparent;
  509. // }
  510. @include until($tablet) {
  511. height: $editor-ascii-height-mobile;
  512. }
  513. > div {
  514. outline: none;
  515. }
  516. p.line {
  517. overflow-wrap: break-word;
  518. }
  519. .tabset {
  520. background-color: mc('teal', '700');
  521. color: mc('teal', '100') !important;
  522. padding: 5px 12px;
  523. font-size: 14px;
  524. font-weight: 500;
  525. border-radius: 5px 0 0 0;
  526. font-style: italic;
  527. &::after {
  528. display: none;
  529. }
  530. &-header {
  531. background-color: mc('teal', '500');
  532. color: #FFF !important;
  533. padding: 5px 12px;
  534. font-size: 14px;
  535. font-weight: 500;
  536. margin-top: 0 !important;
  537. &::after {
  538. display: none;
  539. }
  540. }
  541. &-content {
  542. border-left: 5px solid mc('teal', '500');
  543. background-color: mc('teal', '50');
  544. padding: 0 15px 15px;
  545. overflow: hidden;
  546. @at-root .theme--dark & {
  547. background-color: rgba(mc('teal', '500'), .1);
  548. }
  549. }
  550. }
  551. }
  552. }
  553. &-toolbar {
  554. background-color: mc('blue', '700');
  555. background-image: linear-gradient(to bottom, mc('blue', '700') 0%, mc('blue','800') 100%);
  556. color: #FFF;
  557. .v-toolbar__content {
  558. padding-left: 64px;
  559. @include until($tablet) {
  560. padding-left: 8px;
  561. }
  562. }
  563. }
  564. &-insert:not(.v-speed-dial--right) {
  565. @include from($tablet) {
  566. left: 50%;
  567. margin-left: -28px;
  568. }
  569. }
  570. &-sidebar {
  571. background-color: mc('grey', '900');
  572. width: 64px;
  573. display: flex;
  574. flex-direction: column;
  575. justify-content: flex-start;
  576. align-items: center;
  577. padding: 24px 0;
  578. @include until($tablet) {
  579. padding: 12px 0;
  580. width: 40px;
  581. }
  582. }
  583. &-sysbar {
  584. padding-left: 0;
  585. &-locale {
  586. background-color: rgba(255,255,255,.25);
  587. display:inline-flex;
  588. padding: 0 12px;
  589. height: 24px;
  590. width: 63px;
  591. justify-content: center;
  592. align-items: center;
  593. }
  594. }
  595. // ==========================================
  596. // Fix FAB revealing under codemirror
  597. // ==========================================
  598. .speed-dial--fixed {
  599. z-index: 8;
  600. }
  601. // ==========================================
  602. // CODE MIRROR
  603. // ==========================================
  604. .CodeMirror {
  605. height: auto;
  606. font-family: 'Roboto Mono', monospace;
  607. font-size: .9rem;
  608. .cm-header-1 {
  609. font-size: 1.5rem;
  610. }
  611. .cm-header-2 {
  612. font-size: 1.25rem;
  613. }
  614. .cm-header-3 {
  615. font-size: 1.15rem;
  616. }
  617. .cm-header-4 {
  618. font-size: 1.1rem;
  619. }
  620. .cm-header-5 {
  621. font-size: 1.05rem;
  622. }
  623. .cm-header-6 {
  624. font-size: 1.025rem;
  625. }
  626. }
  627. .CodeMirror-wrap pre.CodeMirror-line, .CodeMirror-wrap pre.CodeMirror-line-like {
  628. word-break: break-word;
  629. }
  630. .CodeMirror-focused .cm-matchhighlight {
  631. background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
  632. background-position: bottom;
  633. background-repeat: repeat-x;
  634. }
  635. .cm-matchhighlight {
  636. background-color: mc('grey', '800');
  637. }
  638. .CodeMirror-selection-highlight-scrollbar {
  639. background-color: mc('green', '600');
  640. }
  641. }
  642. </style>