@ -1,19 +1,6 @@
@@ -1,19 +1,6 @@
import listEditor from 'wiki-list-editor' ;
import MarkdownIt from 'markdown-it' ;
import MarkdownItWikilinks from './wikilinks' ;
import MarkdownItMark from 'markdown-it-mark' ;
import MarkdownItKatex from 'markdown-it-katex' ;
import axios from 'axios' ;
import qs from 'querystring'
import $ from 'jquery' ;
import search from './search' ;
import createPageSearch from './fuse' ;
import util from './util' ;
import Mustache from 'mustache' ;
import getCaretCoordinates from './caret-position'
import moment from 'moment'
import mermaid from 'mermaid'
import { Network , parseDOTNetwork } from "vis-network/peer" ;
import PrismJS from 'prismjs'
import 'prismjs/plugins/filter-highlight-all/prism-filter-highlight-all'
import 'prismjs/components/prism-php'
@ -22,13 +9,10 @@ import 'prismjs/components/prism-perl'
@@ -22,13 +9,10 @@ import 'prismjs/components/prism-perl'
import 'prismjs/components/prism-css'
import 'prismjs/components/prism-markup-templating'
import 'prismjs/components/prism-jq'
import menu from './menu.js'
import './styles.scss'
import wikiGraph from './graph'
import { create , all } from 'mathjs'
const math = create ( all )
import Editor from './editor'
import MD from './markdown'
import 'katex/dist/katex.min.css'
moment . locale ( 'nl' )
mermaid . initialize ( { startOnLoad : true } )
@ -40,122 +24,6 @@ PrismJS.plugins.filterHighlightAll.reject.addSelector('.language-dot')
@@ -40,122 +24,6 @@ PrismJS.plugins.filterHighlightAll.reject.addSelector('.language-dot')
// wikiGraph('.graph-network')
// })
function isMultiline ( input ) {
return input . value . startsWith ( "```" , 0 )
}
function addSaver ( editor , saveUrl , page , beforeSave ) {
return {
save ( ) {
return editor . save ( ) . then ( outputData => {
beforeSave ( )
let data = {
'json' : 1 ,
'p' : page ,
'summary' : "" ,
'content' : JSON . stringify ( outputData ) ,
} ;
return axios . post ( saveUrl , qs . encode ( data ) )
} )
}
}
}
function Indicator ( element , timeout ) {
let timeoutId ;
return {
done ( ) {
timeoutId = setTimeout ( ( ) => {
element . classList . add ( 'hide' )
} , timeout * 1000 ) ;
} ,
setText ( text ) {
if ( timeoutId ) {
clearTimeout ( timeoutId ) ;
timeoutId = null ;
}
element . innerText = text ;
element . classList . remove ( 'hide' )
} ,
}
}
function addIndicator ( editor , indicator ) {
return {
save ( ) {
editor . save ( ) . then ( ( ) => {
indicator . setText ( 'saved!' ) ;
indicator . done ( ) ;
} )
}
}
}
function showSearchResults ( searchTool , query , input , value , resultType ) {
return showSearchResultsExtended ( '#link-complete' , 'link-template' , searchTool , query , input , value , resultType , {
showOnlyResults : true ,
belowCursor : true
} )
}
function showSearchResultsExtended ( element , template , searchTool , query , input , value , resultType , options ) {
const $lc = $ ( element )
return searchTool ( query ) . then ( results => {
let opt = options || { } ;
if ( opt . showOnlyResults && ( query . length === 0 || ! results . length ) ) {
$lc . fadeOut ( )
return
}
$lc . data ( 'result-type' , resultType )
if ( opt . belowCursor ) {
let pos = getCaretCoordinates ( input , value . selectionEnd , { } )
let off = $ ( input ) . offset ( )
pos . top += off . top + pos . height
pos . left += off . left
$lc . offset ( pos )
}
var templateText = document . getElementById ( template ) . innerHTML ;
var rendered = Mustache . render ( templateText , {
page : value . trim ( ) . replace ( /\s+/g , '_' ) ,
results : results
} , { } , [ '[[' , ']]' ] ) ;
let selected = $lc . find ( 'li.selected' ) ;
if ( selected ) {
let selectedPos = $lc . find ( 'li' ) . index ( selected [ 0 ] )
rendered = $ ( rendered )
const $lis = $lc . find ( 'li' )
if ( $lis . length >= 1 ) {
selectedPos = Math . min ( selectedPos , $lis . length - 1 )
rendered . find ( 'li' ) [ selectedPos ] . classList . add ( 'selected' )
}
}
$lc . html ( rendered ) . fadeIn ( )
return results
} )
}
function renderGraphs ( ) {
$ ( 'code.language-dot' ) . each ( function ( i , code ) {
if ( ! code . innerText ) {
return true
}
let data = parseDOTNetwork ( code . innerText )
let network = new Network ( code , data , {
layout : {
randomSeed : 1239043
}
} ) ;
$ ( code ) . on ( 'click' , function ( ) {
return false
} )
} )
}
/ *
* EVENTS
* /
@ -196,318 +64,17 @@ $(document).on('popup:selected', '#autocomplete', function (event, linkName, res
@@ -196,318 +64,17 @@ $(document).on('popup:selected', '#autocomplete', function (event, linkName, res
}
} )
const MD = new MarkdownIt ( {
linkify : true ,
highlight : function ( str , lang ) {
if ( lang === 'mermaid' ) {
return '<div class="mermaid">' + str + '</div>' ;
}
return '' ;
}
} )
MD . use ( MarkdownItWikilinks ( {
baseURL : document . querySelector ( 'body' ) . dataset . baseUrl ,
uriSuffix : '' ,
relativeBaseURL : '/edit/' ,
htmlAttributes : {
class : 'wiki-link'
} ,
} ) ) . use ( MarkdownItMark ) . use ( MarkdownItKatex )
let holders = document . getElementsByClassName ( 'wiki-list-editor' ) ;
_ . forEach ( holders , async ( item , i ) => {
new Editor ( item ) . then ( editor => editor . start ( ) ) ;
} )
function Editor ( holder ) {
let scope = { }
const options = {
transform ( text , element ) {
let converted = text
if ( converted . startsWith ( "```" , 0 ) || converted . startsWith ( "$$" , 0 ) ) {
converted = MD . render ( converted )
} else if ( converted . startsWith ( "=" , 0 ) ) {
try {
converted = math . evaluate ( converted . substring ( 1 ) , scope ) . toString ( )
} catch ( e ) {
converted = converted + ' <span style="background: red; color: white;">' + e . message + '</span>' ;
}
} else {
if ( text . match ( /^(\w+):: (.+)$/ ) ) {
converted = converted . replace ( /^(\w+):: (.*)$/ , '**[[$1]]**: $2' )
} else if ( text . match ( /#\[\[TODO]]/ ) ) {
converted = converted . replace ( '#[[TODO]]' , '<input class="checkbox" type="checkbox" />' )
} else if ( text . match ( /#\[\[DONE]]/ ) ) {
converted = converted . replace ( '#[[DONE]]' , '<input class="checkbox" type="checkbox" checked />' )
}
MD . options . html = true
converted = MD . renderInline ( converted )
MD . options . html = false
}
element . html ( converted )
}
}
let inputData = JSON . parse ( holder . dataset . input )
let editor = listEditor ( holder , inputData , options ) ;
holder . $listEditor = editor
$ ( holder ) . on ( 'click' , '.content input[type="checkbox"]' , function ( event ) {
let that = this
let id = $ ( this ) . closest ( '.list-item' ) . data ( 'id' )
editor . update ( id , function ( item , prev , next ) {
if ( that . checked ) {
item . text = item . text . replace ( '#[[TODO]]' , '#[[DONE]]' )
} else {
item . text = item . text . replace ( '#[[DONE]]' , '#[[TODO]]' )
}
return item
} ) ;
event . stopPropagation ( )
return true
} )
editor . on ( 'change' , function ( ) {
let element = holder
let indicator = Indicator ( document . getElementById ( 'save-indicator' ) , 2 ) ;
let saveUrl = element . dataset . saveurl ;
let page = element . dataset . page ;
indicator . setText ( 'has changes...' ) ;
addIndicator (
addSaver ( editor , saveUrl , page , ( ) => indicator . setText ( 'saving...' ) ) ,
indicator
) . save ( )
} )
editor . on ( 'rendered' , function ( ) {
PrismJS . highlightAll ( )
mermaid . init ( )
renderGraphs ( ) ;
} )
menu . connectContextMenu ( editor )
return createPageSearch ( ) . then ( function ( { titleSearch , commandSearch , commands } ) {
editor . on ( 'start-editing' , function ( input ) {
const $lc = $ ( '#link-complete' ) ;
$ ( input ) . parents ( '.list-item' ) . addClass ( 'active' ) ;
$lc . on ( 'popup:selected' , function ( event , linkName , resultType , element ) {
let value = input . value
let end = input . selectionEnd
if ( resultType === 'link' ) {
let start = value . lastIndexOf ( "[[" , end )
end += 2
let startAndLink = value . substring ( 0 , start ) + "[[" + linkName + "]]"
input . value = startAndLink + value . substring ( end )
input . selectionStart = startAndLink . length
input . selectionEnd = startAndLink . length
input . focus ( )
} else if ( resultType === 'command' ) {
let start = value . lastIndexOf ( "/" , end )
let commandResult = ""
let replace = ""
let prefix = false
let adjustment = 0
let now = moment ( )
if ( linkName === "Current Time" ) {
commandResult = now . format ( 'HH:mm' )
} else if ( linkName === "Today" ) {
commandResult = "[[" + now . format ( 'D MMMM YYYY' ) + "]]"
} else if ( linkName === "Tomorrow" ) {
commandResult = "[[" + now . add ( 1 , 'day' ) . format ( 'D MMMM YYYY' ) + "]]"
} else if ( linkName === "Yesterday" ) {
commandResult = "[[" + now . add ( - 1 , 'day' ) . format ( 'D MMMM YYYY' ) + "]]"
} else if ( linkName === "TODO" ) {
commandResult = "#[[TODO]] "
replace = "#[[DONE]] "
prefix = true
} else if ( linkName === "DONE" ) {
commandResult = "#[[DONE]] "
replace = "#[[TODO]] "
prefix = true
} else if ( linkName === "Page Reference" ) {
commandResult = "[[]]"
adjustment = - 2
} else if ( linkName === "Code Block" ) {
commandResult = "```\n\n```"
adjustment = - 5
}
let startAndLink = prefix
? commandResult + value . substring ( 0 , start ) . replace ( replace , "" )
: value . substring ( 0 , start ) + commandResult
input . value = startAndLink + value . substring ( end )
input . selectionStart = startAndLink . length + adjustment
input . selectionEnd = startAndLink . length + adjustment
input . focus ( )
}
return true
} )
$lc . on ( 'popup:leave' , function ( event ) {
input . focus ( )
} )
$ ( input ) . on ( 'keydown' , function ( event ) {
const isVisible = $ ( '#link-complete:visible' ) . length > 0 ;
if ( event . key === 'Escape' && isVisible ) {
$lc . fadeOut ( )
return false
} else if ( event . key === 'Enter' && isVisible ) {
const element = $lc . find ( 'li.selected' )
const linkName = element . text ( )
$lc . trigger ( 'popup:selected' , [ linkName , $lc . data ( 'result-type' ) , element ] )
$lc . fadeOut ( )
return false
} else if ( event . key === 'ArrowUp' && isVisible ) {
const selected = $lc . find ( 'li.selected' )
const prev = selected . prev ( 'li' )
if ( prev . length ) {
prev . addClass ( 'selected' )
selected . removeClass ( 'selected' )
prev [ 0 ] . scrollIntoView ( { block : 'center' , inline : 'nearest' } )
} else {
// move back from dropdown to input
$lc . trigger ( 'popup:leave' )
selected . removeClass ( 'selected' )
}
return false
} else if ( event . key === 'ArrowDown' && isVisible ) {
const selected = $lc . find ( 'li.selected' )
if ( ! selected . length ) {
$lc . find ( 'li:not(.selected):first-child' ) . addClass ( 'selected' )
return false
}
const next = selected . next ( 'li' ) ;
if ( next . length ) {
next . addClass ( 'selected' )
selected . removeClass ( 'selected' )
next [ 0 ] . scrollIntoView ( { block : 'center' , inline : 'nearest' } )
}
return false
}
let mirror = {
'[' : ']' ,
'(' : ')' ,
'{' : '}' ,
}
if ( ! isMultiline ( input ) && mirror . hasOwnProperty ( event . key ) ) {
let input = this
let val = input . value
let prefix = val . substring ( 0 , input . selectionStart )
let selection = val . substring ( input . selectionStart , input . selectionEnd )
let suffix = val . substring ( input . selectionEnd )
input . value = prefix + event . key + selection + mirror [ event . key ] + suffix
input . selectionStart = prefix . length + event . key . length
input . selectionEnd = input . selectionStart + selection . length
$ ( input ) . trigger ( 'input' )
return false ;
}
return true
} )
let searchEnabled = false
$ ( input ) . on ( 'keyup' , function ( event ) {
if ( event . key === '/' ) {
searchEnabled = true
}
if ( searchEnabled && event . key === 'Escape' ) {
searchEnabled = false
return false ;
}
const ignoreKeys = {
'ArrowUp' : true ,
'ArrowDown' : true ,
'Enter' : true ,
}
if ( event . key in ignoreKeys ) {
return false
}
let value = input . value
let end = input . selectionEnd
let [ start , insideLink ] = util . cursorInsideLink ( value , end )
let insideSearch = false
if ( searchEnabled && ! insideLink ) {
start = value . lastIndexOf ( "/" , end )
insideSearch = start >= 0
}
if ( insideSearch ) {
let query = value . substring ( start + 1 , end ) ;
showSearchResults ( commandSearch , query , input , value , 'command' ) . then ( results => {
if ( query . length > 0 && result . length === 0 ) {
searchEnabled = false
}
} )
return true
} else if ( insideLink ) {
let query = value . substring ( start + 2 , end ) ;
showSearchResults ( titleSearch , query , input , value , 'link' ) ;
return true
document . querySelectorAll ( ".page-loader" )
. forEach ( ( el , key , parent ) => {
let format = el . dataset . format
let edit = el . dataset . edit
fetch ( '/' + el . dataset . page + '?format=' + format )
. then ( res => edit ? res . json ( ) : res . text ( ) )
. then ( text => {
if ( edit ) {
new Editor ( el , text ) . then ( editor => editor . start ( ) ) ;
} else {
$ ( '#link-complete' ) . fadeOut ( ) ;
el . innerHTML = MD . render ( text )
}
} )
} )
editor . on ( 'stop-editing' , function ( input ) {
$ ( input ) . parents ( '.list-item' ) . removeClass ( 'active' ) ;
$ ( '#link-complete' ) . off ( )
PrismJS . highlightAll ( )
mermaid . init ( )
renderGraphs ( ) ;
} )
return editor
} )
}
let timeout = null ;
let searchInput = document . getElementById ( 'search-input' ) ;
search ( searchInput ) . then ( searcher => {
let showSearch = _ . debounce ( function ( searcher ) {
let query = $ ( searchInput ) . val ( )
if ( query === '' ) {
let autocomplete = document . getElementById ( 'autocomplete' ) ;
$ ( autocomplete ) . hide ( )
return false
}
showSearchResultsExtended ( '#autocomplete' , 'result-template' , query => searcher . search ( query ) , query , searchInput , query , 'search-result' )
return true ;
} , 200 )
$ ( searchInput ) . on ( 'keyup' , function ( event ) {
showSearch . cancel ( )
showSearch ( searcher )
return true
} )
} )
document . querySelectorAll ( ".page-loader" )
. forEach ( ( el , key , parent ) => {
fetch ( '/' + el . dataset . page + '?format=markdown' ) . then ( res => res . text ( ) ) . then ( text => {
el . innerHTML = MD . render ( text )
} )
} )