Skip to main content

Wiki Edits (Bookstack)

This page provides a collection of useful code snippets for the Wiki. The Wiki used in question is the open-source 'Bookstack'.

Dark Design Enforcement

It is possible to default to dark magic and only provide a dark theme experience to your users. 
Needed are two things, the parameter for docker and some CSS:

APP_DEFAULT_DARK_MODE=true
form:has(svg[data-icon="light-mode"], form[action*="toggle-dark-mode"] ) {
    display: none !important;
}

nav > div.dropdown-container > ul > li[role="presentation"] {
    display: none !important;
}

Be advised that the theme-selection is cached and any user might still use a light theme if their session is active and the preference is thus being enforced.   


Own Theme for CodeMirror

To provide CodeMirror with a own theme there are several things possible.

The docs for this are located at https://www.bookstackapp.com/docs/admin/visual-customisation/#changing-code-block-themes
Below is a simple javascript of the code highlighting used here that you can put into the "Custom HTML Head Content":

<script>
const background        = '#182534',  // editor background
      highlightBg       = '#252D3C',  // active line / selection highlight
      surface           = '#244497',  // tooltips / panels
      mutedText         = '#5C7088',  // dimmed text (comments etc.)
      softText          = '#7C90A0',  // secondary text
      accentText        = '#9FB4C5',  // softer accents
      mainText          = '#F5F7FA',  // main foreground text
      brightText        = '#FFFFFF',  // pure white for emphasis

      redAccent         = '#FF4C4C',  // errors / critical
      orangeAccent      = '#FF9D5C',  // annotations / classes
      yellowAccent      = '#FFD43B',  // constants / tags
      greenAccent       = '#6EEB83',  // emphasis / italic / inserted
      mintString        = '#4ADE80',  // strings
      cyanAccent        = '#2AD4D4',  // functions / properties
      tealAccent        = '#1FA2D6',  // adjusted keyword color (no conflict with outside blue)
      violetAccent      = '#A86CFF',  // operators / definitions
      magentaAccent     = '#E44897';  // brighter magenta for numbers

const invalid           = redAccent,
      cursor            = accentText;

function viewThemeBuilder() {
    return {
      '&':{color:mainText,backgroundColor:background},
      '.cm-content':{caretColor:cursor},
      '.cm-cursor, .cm-dropCursor':{borderLeftColor:cursor},
      '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':{backgroundColor:greenAccent},
      '.cm-panels':{backgroundColor:background,color:softText},
      '.cm-panels.cm-panels-top':{borderBottom:'2px solid black'},
      '.cm-panels.cm-panels-bottom':{borderTop:'2px solid black'},
      '.cm-searchMatch':{backgroundColor:'#72a1ff59',outline:'1px solid #457dff'},
      '.cm-searchMatch.cm-searchMatch-selected':{backgroundColor:'#6199ff2f'},
      '.cm-activeLine':{backgroundColor:highlightBg},
      '.cm-selectionMatch':{backgroundColor:'#aafe661a'},
      '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket':{outline:`1px solid ${tealAccent}`},
      '.cm-gutters':{backgroundColor:background,color:accentText,border:'none'},
      '.cm-activeLineGutter':{backgroundColor:highlightBg},
      '.cm-foldPlaceholder':{backgroundColor:'transparent',border:'none',color:'#ddd'},
      '.cm-tooltip':{border:'none',backgroundColor:surface},
      '.cm-tooltip .cm-tooltip-arrow:before':{borderTopColor:'transparent',borderBottomColor:'transparent'},
      '.cm-tooltip .cm-tooltip-arrow:after':{borderTopColor:surface,borderBottomColor:surface},
      '.cm-tooltip-autocomplete':{
        '& > ul > li[aria-selected]':{backgroundColor:highlightBg,color:softText}
      }
    };
}

function highlightStyleBuilder(t) {
    return [
      {tag:t.keyword,                             color:tealAccent},      // keywords = teal
      {tag:[t.name,t.propertyName,t.macroName],   color:cyanAccent},      // properties / macros = cyan
      {tag:[t.variableName],                      color:mainText},        // plain variables = main text
      {tag:[t.function(t.variableName)],          color:violetAccent},    // function names = violet
      {tag:[t.labelName],                         color:magentaAccent},   // labels = magenta
      {tag:[t.color,t.constant(t.name)],          color:yellowAccent},    // constants = yellow
      {tag:[t.definition(t.name),t.separator],    color:orangeAccent},    // definitions & separators = orange
      {tag:[t.brace],                             color:cyanAccent},      // braces = cyan
      {tag:[t.annotation],                        color:orangeAccent},    // annotations = orange
      {tag:[t.number,t.changed,t.modifier,t.namespace], color:magentaAccent}, // numbers = magenta (rule)
      {tag:[t.typeName,t.className],              color:orangeAccent},    // classes / types = orange
      {tag:[t.operator,t.operatorKeyword],        color:violetAccent},    // operators = violet
      {tag:[t.tagName],                           color:yellowAccent},    // HTML/XML tags = yellow
      {tag:[t.squareBracket],                     color:redAccent},       // square brackets = red
      {tag:[t.angleBracket],                      color:softText},        // angle brackets = muted
      {tag:[t.attributeName],                     color:cyanAccent},      // attribute names = cyan
      {tag:[t.regexp],                            color:redAccent},       // regex = red
      {tag:[t.quote],                             color:greenAccent},     // quotes = green
      {tag:[t.string],                            color:mintString},      // strings = mint-green
      {tag:t.link,                                color:cyanAccent,textDecoration:'underline',textUnderlinePosition:'under'},
      {tag:[t.url,t.escape,t.special(t.string)],  color:yellowAccent},
      {tag:[t.meta],                              color:redAccent},
      {tag:[t.comment],                           color:mutedText,fontStyle:'italic'},
      {tag:t.strong,                              fontWeight:'bold',color:brightText},
      {tag:t.emphasis,                            fontStyle:'italic',color:greenAccent},
      {tag:t.strikethrough,                       textDecoration:'line-through'},
      {tag:t.heading,                             fontWeight:'bold',color:yellowAccent},
      {tag:t.heading1,                            fontWeight:'bold',color:brightText},
      {tag:[t.heading2,t.heading3,t.heading4],    fontWeight:'bold',color:tealAccent},
      {tag:[t.heading5,t.heading6],               color:violetAccent},
      {tag:[t.atom,t.bool,t.special(t.variableName)], color:magentaAccent}, // atoms = magenta
      {tag:[t.processingInstruction,t.inserted,t.contentSeparator], color:greenAccent},
      {tag:[t.contentSeparator],                  color:yellowAccent},
      {tag:t.invalid,                             color:softText,borderBottom:`1px dotted ${redAccent}`}
    ];
}

window.addEventListener('library-cm6::configure-theme', event => {
    const detail = event.detail;
    detail.registerViewTheme(viewThemeBuilder);
    detail.registerHighlightStyle(highlightStyleBuilder);
});
</script>