import listEditor from 'wiki-list-editor'; import MarkdownIt from 'markdown-it'; import MarkdownItWikilinks from './wikilinks'; import MarkdownItMark from 'markdown-it-mark'; 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 'jquery-contextmenu'; import getCaretCoordinates from './caret-position' import moment from 'moment' import PrismJS from 'prismjs' import 'prismjs/components/prism-php' import 'prismjs/components/prism-markup-templating' import './styles.scss'; import '../node_modules/jquery-contextmenu/dist/jquery.contextMenu.css'; moment.locale('nl') 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('hidden') }, timeout * 1000); }, setText(text) { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } element.innerText = text; element.classList.remove('hidden') }, } } function addIndicator(editor, indicator) { return { save() { editor.save().then(() => { indicator.setText('saved!'); indicator.done(); }) } } } let holder = document.getElementById('editor'); function showSearchResults(searchTool, query, input, value, resultType) { showSearchResultsExtended('#link-complete', 'link-template', searchTool, query, input, value, resultType, {belowCursor: true}) } function showSearchResultsExtended(element, template, searchTool, query, input, value, resultType, options) { const $lc = $(element) let results = searchTool(query) if (query.length === 0 || !results.length) { $lc.fadeOut() return } let opt = options || {}; $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, results: results}, {}, ['[[', ']]']); $lc.html(rendered).fadeIn() } $(document).on('keydown', '.keyboard-list', function (event) { console.log('keydown .keyboard-list') if (!$(':visible', this).length) { return true } const $popup = $(this) if (event.key === 'Escape') { $popup.fadeOut() return false } if (event.key === 'Enter') { const element = $popup.find('li.selected') const linkName = element.text() $popup.trigger('popup:selected', [linkName, $popup.data('result-type'), element]) $popup.fadeOut() return false } if (event.key === 'ArrowUp') { const selected = $popup.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 $popup.trigger('popup:leave') selected.removeClass('selected') } return false } if (event.key === 'ArrowDown') { const selected = $popup.find('li.selected') const next = selected.next('li'); if (next.length) { next.addClass('selected') selected.removeClass('selected') next[0].scrollIntoView({block: 'center', inline: 'nearest'}) } return false } return true }) $(document).on('keydown', '#search-input', function (event) { console.log('keydown #search-input') let $ac = $('#autocomplete'); let isVisible = $(':visible', $ac) if (event.key === 'ArrowDown' && isVisible) { $ac.focus() $ac.find('li:first-child').addClass('selected') return false } if (event.key === 'Escape') { $(searchInput).val(''); $ac.fadeOut(); return false; } return true }) $(document).on('popup:selected', '#autocomplete', function (event, linkName, resultType, element) { if (resultType === 'search-result') { element.find('a')[0].click() return false; } }) if (holder) { console.log(holder.dataset) const MD = new MarkdownIt() MD.use(MarkdownItWikilinks({ baseURL: holder.dataset.baseUrl, uriSuffix: '', relativeBaseURL: '/edit/', htmlAttributes: { class: 'wiki-link' } })).use(MarkdownItMark) const options = { transform(text, callback) { let converted = (text.startsWith("```", 0)) ? MD.render(text) : MD.renderInline(text) return callback(converted) } } let editor = listEditor(holder, JSON.parse(holder.dataset.input), options); editor.on('change', function () { let element = document.getElementById('editor'); 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() }) 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 } if (event.key === 'ArrowDown' && isVisible) { $lc.focus() $lc.find('li:first-child').addClass('selected') 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 }) $(input).on('keyup', function () { let value = input.value let end = input.selectionEnd let [start, insideLink] = util.cursorInsideLink(value, end) console.log(value, end, start, insideLink) let insideSearch = false if (!insideLink) { start = value.lastIndexOf("/", end) insideSearch = start >= 0 } if (insideSearch) { let query = value.substring(start + 1, end); showSearchResults(query => commandSearch.search(query), query, input, value, 'command'); return true } else if (insideLink) { let query = value.substring(start + 2, end); showSearchResults(query => titleSearch.search(query), query, input, value, 'link'); return true } else { $('#link-complete').fadeOut(); } }) }) editor.on('stop-editing', function (input) { $(input).parents('.list-item').removeClass('active'); $('#link-complete').off() PrismJS.highlightAll() }) }) $.contextMenu({ selector: '.marker', items: { copy: { name: 'Copy', callback: function (key, opt) { editor.copy(this).then(result => { console.log(result) }) } } } }); } let timeout = null; let searchInput = document.getElementById('search-input'); search(searchInput).then(searcher => { $(document).on('keyup', '#search-input', function (event) { clearTimeout(timeout); timeout = setTimeout(function () { 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') // $('#autocomplete').show() // let result = searcher.search(query) // const newpage = query.replace(/\s+/g, '_') // var template = document.getElementById('result-template').innerHTML; // var rendered = Mustache.render(template, {page: newpage, results: result}, {}, ['[[', ']]']); // let autocomplete = document.getElementById('autocomplete'); // autocomplete.innerHTML = rendered; }, 200) return true }) })