Add autocomplete of page links
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
7956df26b4
commit
f2cc16341d
14
backref.go
14
backref.go
|
@ -91,10 +91,18 @@ func loadBackrefs(fp *FilePages, p string) (map[string][]Backref, error) {
|
||||||
links := renderLinks(strings.TrimLeft(ref.Link.Line, " *"))
|
links := renderLinks(strings.TrimLeft(ref.Link.Line, " *"))
|
||||||
pageText := renderMarkdown2(links)
|
pageText := renderMarkdown2(links)
|
||||||
|
|
||||||
|
removeBrackets := func(r rune) rune {
|
||||||
|
if r == '[' || r == ']' || r == '*' {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
result[ref.Name] = append(result[ref.Name], Backref{
|
result[ref.Name] = append(result[ref.Name], Backref{
|
||||||
Name: ref.Name,
|
Name: ref.Name,
|
||||||
Title: title,
|
Title: title,
|
||||||
Line: template.HTML(pageText),
|
LineHTML: template.HTML(pageText),
|
||||||
|
Line: strings.Map(removeBrackets, ref.Link.Line),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
94
editor/package-lock.json
generated
94
editor/package-lock.json
generated
|
@ -1093,6 +1093,14 @@
|
||||||
"del": "^4.1.1"
|
"del": "^4.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cli": {
|
||||||
|
"version": "0.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/cli/-/cli-0.4.3.tgz",
|
||||||
|
"integrity": "sha1-5oGcjV+qlX9k+Y9mqFBiaMHR8X0=",
|
||||||
|
"requires": {
|
||||||
|
"glob": ">= 3.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cliui": {
|
"cliui": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||||
|
@ -1141,6 +1149,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
},
|
},
|
||||||
|
"colors": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
|
||||||
|
},
|
||||||
"combined-stream": {
|
"combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
@ -2935,6 +2948,24 @@
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"fuse": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fuse/-/fuse-0.4.0.tgz",
|
||||||
|
"integrity": "sha1-LDjq+IirsKm6eWDP4zOdHz9T9uY=",
|
||||||
|
"requires": {
|
||||||
|
"colors": ">=0.6.x",
|
||||||
|
"fuse.js": "^6.0.0",
|
||||||
|
"jshint": "0.9.x",
|
||||||
|
"optimist": ">=0.3.5",
|
||||||
|
"uglify-js": ">=2.2.x",
|
||||||
|
"underscore": ">=1.4.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fuse.js": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-e5Ap6mhF/WQ9bKqsMFTTR5/DS9qbYab4VXHtMdxCanH+VZkdUV2LqcgMO31etSQv53NXsguQF1bdqkrrPAM2HQ=="
|
||||||
|
},
|
||||||
"gauge": {
|
"gauge": {
|
||||||
"version": "2.7.4",
|
"version": "2.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||||
|
@ -3857,6 +3888,30 @@
|
||||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||||
},
|
},
|
||||||
|
"jshint": {
|
||||||
|
"version": "0.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jshint/-/jshint-0.9.1.tgz",
|
||||||
|
"integrity": "sha1-/zLsfwn4QAH3SY7q/WPJ5Puy3A4=",
|
||||||
|
"requires": {
|
||||||
|
"cli": "0.4.3",
|
||||||
|
"minimatch": "0.0.x"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-qlD5cEdCKsclQ72hd6nJ0BjZhFI="
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.0.5.tgz",
|
||||||
|
"integrity": "sha1-lrtJC707poNrv6wRGt91MBsVhN4=",
|
||||||
|
"requires": {
|
||||||
|
"lru-cache": "~1.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"json-parse-better-errors": {
|
"json-parse-better-errors": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||||
|
@ -4744,6 +4799,22 @@
|
||||||
"is-wsl": "^1.1.0"
|
"is-wsl": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"optimist": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||||
|
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
|
||||||
|
"requires": {
|
||||||
|
"minimist": "~0.0.1",
|
||||||
|
"wordwrap": "~0.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||||
|
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
|
||||||
|
@ -6892,7 +6963,6 @@
|
||||||
"version": "3.4.10",
|
"version": "3.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
|
||||||
"integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
|
"integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"commander": "~2.19.0",
|
"commander": "~2.19.0",
|
||||||
"source-map": "~0.6.1"
|
"source-map": "~0.6.1"
|
||||||
|
@ -6901,17 +6971,20 @@
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
||||||
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
|
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||||
"dev": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"underscore": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg=="
|
||||||
|
},
|
||||||
"union-value": {
|
"union-value": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||||
|
@ -7538,15 +7611,20 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wiki-list-editor": {
|
"wiki-list-editor": {
|
||||||
"version": "0.6.4",
|
"version": "0.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/wiki-list-editor/-/wiki-list-editor-0.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/wiki-list-editor/-/wiki-list-editor-0.6.5.tgz",
|
||||||
"integrity": "sha512-UkCrS62HR6/Slk2jggxQZryoe3YEbBPrceJ76sl6a4YPni/xWCajLGl/qLsUemQMwyoVI7ApAmLE9GvJeLLPyQ==",
|
"integrity": "sha512-1/XQ7JSJoet/TCq5RiF0JAHFZYveI3fEW8drc6gPnAfwz4e2t7vHBZV5sDhFO+z4fD+uISfsBveW4oJzVNteNA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"dragula": "^3.7.2",
|
"dragula": "^3.7.2",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
"lodash": "^4.17.15"
|
"lodash": "^4.17.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"wordwrap": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||||
|
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
|
||||||
|
},
|
||||||
"worker-farm": {
|
"worker-farm": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
|
||||||
|
|
|
@ -12,13 +12,15 @@
|
||||||
"bulma": "^0.7.5",
|
"bulma": "^0.7.5",
|
||||||
"css-loader": "^3.2.0",
|
"css-loader": "^3.2.0",
|
||||||
"file-loader": "^6.0.0",
|
"file-loader": "^6.0.0",
|
||||||
|
"fuse": "^0.4.0",
|
||||||
|
"fuse.js": "^6.0.0",
|
||||||
"jquery-contextmenu": "^2.9.2",
|
"jquery-contextmenu": "^2.9.2",
|
||||||
"lunr": "^2.3.8",
|
"lunr": "^2.3.8",
|
||||||
"mustache": "^4.0.1",
|
"mustache": "^4.0.1",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"sass-loader": "^7.3.1",
|
"sass-loader": "^7.3.1",
|
||||||
"style-loader": "^1.0.0",
|
"style-loader": "^1.0.0",
|
||||||
"wiki-list-editor": "^0.6.4"
|
"wiki-list-editor": "^0.6.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
|
145
editor/src/caret-position.js
Normal file
145
editor/src/caret-position.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
// We'll copy the properties below into the mirror div.
|
||||||
|
// Note that some browsers, such as Firefox, do not concatenate properties
|
||||||
|
// into their shorthand (e.g. padding-top, padding-bottom etc. -> padding),
|
||||||
|
// so we have to list every single property explicitly.
|
||||||
|
var properties = [
|
||||||
|
'direction', // RTL support
|
||||||
|
'boxSizing',
|
||||||
|
'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
|
||||||
|
'height',
|
||||||
|
'overflowX',
|
||||||
|
'overflowY', // copy the scrollbar for IE
|
||||||
|
|
||||||
|
'borderTopWidth',
|
||||||
|
'borderRightWidth',
|
||||||
|
'borderBottomWidth',
|
||||||
|
'borderLeftWidth',
|
||||||
|
'borderStyle',
|
||||||
|
|
||||||
|
'paddingTop',
|
||||||
|
'paddingRight',
|
||||||
|
'paddingBottom',
|
||||||
|
'paddingLeft',
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/font
|
||||||
|
'fontStyle',
|
||||||
|
'fontVariant',
|
||||||
|
'fontWeight',
|
||||||
|
'fontStretch',
|
||||||
|
'fontSize',
|
||||||
|
'fontSizeAdjust',
|
||||||
|
'lineHeight',
|
||||||
|
'fontFamily',
|
||||||
|
|
||||||
|
'textAlign',
|
||||||
|
'textTransform',
|
||||||
|
'textIndent',
|
||||||
|
'textDecoration', // might not make a difference, but better be safe
|
||||||
|
|
||||||
|
'letterSpacing',
|
||||||
|
'wordSpacing',
|
||||||
|
|
||||||
|
'tabSize',
|
||||||
|
'MozTabSize'
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
var isBrowser = (typeof window !== 'undefined');
|
||||||
|
var isFirefox = (isBrowser && window.mozInnerScreenX != null);
|
||||||
|
|
||||||
|
function getCaretCoordinates(element, position, options) {
|
||||||
|
if (!isBrowser) {
|
||||||
|
throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
|
||||||
|
}
|
||||||
|
|
||||||
|
var debug = options && options.debug || false;
|
||||||
|
if (debug) {
|
||||||
|
var el = document.querySelector('#input-textarea-caret-position-mirror-div');
|
||||||
|
if (el) el.parentNode.removeChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mirror div will replicate the textarea's style
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.id = 'input-textarea-caret-position-mirror-div';
|
||||||
|
document.body.appendChild(div);
|
||||||
|
|
||||||
|
var style = div.style;
|
||||||
|
var computed = window.getComputedStyle ? window.getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9
|
||||||
|
var isInput = element.nodeName === 'INPUT';
|
||||||
|
|
||||||
|
// Default textarea styles
|
||||||
|
style.whiteSpace = 'pre-wrap';
|
||||||
|
if (!isInput)
|
||||||
|
style.wordWrap = 'break-word'; // only for textarea-s
|
||||||
|
|
||||||
|
// Position off-screen
|
||||||
|
style.position = 'absolute'; // required to return coordinates properly
|
||||||
|
if (!debug)
|
||||||
|
style.visibility = 'hidden'; // not 'display: none' because we want rendering
|
||||||
|
|
||||||
|
// Transfer the element's properties to the div
|
||||||
|
properties.forEach(function (prop) {
|
||||||
|
if (isInput && prop === 'lineHeight') {
|
||||||
|
// Special case for <input>s because text is rendered centered and line height may be != height
|
||||||
|
if (computed.boxSizing === "border-box") {
|
||||||
|
var height = parseInt(computed.height);
|
||||||
|
var outerHeight =
|
||||||
|
parseInt(computed.paddingTop) +
|
||||||
|
parseInt(computed.paddingBottom) +
|
||||||
|
parseInt(computed.borderTopWidth) +
|
||||||
|
parseInt(computed.borderBottomWidth);
|
||||||
|
var targetHeight = outerHeight + parseInt(computed.lineHeight);
|
||||||
|
if (height > targetHeight) {
|
||||||
|
style.lineHeight = height - outerHeight + "px";
|
||||||
|
} else if (height === targetHeight) {
|
||||||
|
style.lineHeight = computed.lineHeight;
|
||||||
|
} else {
|
||||||
|
style.lineHeight = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
style.lineHeight = computed.height;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
style[prop] = computed[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isFirefox) {
|
||||||
|
// Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
|
||||||
|
if (element.scrollHeight > parseInt(computed.height))
|
||||||
|
style.overflowY = 'scroll';
|
||||||
|
} else {
|
||||||
|
style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
|
||||||
|
}
|
||||||
|
|
||||||
|
div.textContent = element.value.substring(0, position);
|
||||||
|
// The second special handling for input type="text" vs textarea:
|
||||||
|
// spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
|
||||||
|
if (isInput)
|
||||||
|
div.textContent = div.textContent.replace(/\s/g, '\u00a0');
|
||||||
|
|
||||||
|
var span = document.createElement('span');
|
||||||
|
// Wrapping must be replicated *exactly*, including when a long word gets
|
||||||
|
// onto the next line, with whitespace at the end of the line before (#7).
|
||||||
|
// The *only* reliable way to do that is to copy the *entire* rest of the
|
||||||
|
// textarea's content into the <span> created at the caret position.
|
||||||
|
// For inputs, just '.' would be enough, but no need to bother.
|
||||||
|
span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
|
||||||
|
div.appendChild(span);
|
||||||
|
|
||||||
|
var coordinates = {
|
||||||
|
top: span.offsetTop + parseInt(computed['borderTopWidth']),
|
||||||
|
left: span.offsetLeft + parseInt(computed['borderLeftWidth']),
|
||||||
|
height: parseInt(computed['lineHeight'])
|
||||||
|
};
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
span.style.backgroundColor = '#aaa';
|
||||||
|
} else {
|
||||||
|
document.body.removeChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCaretCoordinates;
|
20
editor/src/fuse.js
Normal file
20
editor/src/fuse.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import Fuse from 'fuse.js'
|
||||||
|
import $ from 'jquery'
|
||||||
|
|
||||||
|
function createTitleSearch() {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
$.get('/links.json', function (documents) {
|
||||||
|
const options = {
|
||||||
|
keys: ['title'],
|
||||||
|
}
|
||||||
|
let fuse = new Fuse(documents, options)
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
documents,
|
||||||
|
search: fuse,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createTitleSearch
|
|
@ -3,8 +3,10 @@ import axios from 'axios';
|
||||||
import qs from 'querystring'
|
import qs from 'querystring'
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import search from './search';
|
import search from './search';
|
||||||
|
import createPageSearch from './fuse';
|
||||||
import Mustache from 'mustache';
|
import Mustache from 'mustache';
|
||||||
import 'jquery-contextmenu';
|
import 'jquery-contextmenu';
|
||||||
|
import getCaretCoordinates from './caret-position'
|
||||||
|
|
||||||
import './styles.scss';
|
import './styles.scss';
|
||||||
import '../node_modules/jquery-contextmenu/dist/jquery.contextMenu.css';
|
import '../node_modules/jquery-contextmenu/dist/jquery.contextMenu.css';
|
||||||
|
@ -75,6 +77,110 @@ if (holder) {
|
||||||
).save()
|
).save()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$(document).on('keydown', '#link-complete', function (event) {
|
||||||
|
if (!$('#link-complete:visible')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const $popup = $('#link-complete')
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
$popup.fadeOut()
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
const linkName = $popup.find('li.selected').text()
|
||||||
|
$popup.trigger('popup:selected', [linkName])
|
||||||
|
$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')
|
||||||
|
}
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
createPageSearch().then(function ({search}) {
|
||||||
|
editor.on('start-editing', function (input) {
|
||||||
|
const $lc = $('#link-complete')
|
||||||
|
|
||||||
|
$lc.on('popup:selected', function (event, linkName) {
|
||||||
|
let value = input.value
|
||||||
|
let end = input.selectionEnd
|
||||||
|
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()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
$(input).on('keydown', function (event) {
|
||||||
|
const isVisible = $('#link-complete:visible').length > 0;
|
||||||
|
|
||||||
|
const $lc = $('#link-complete')
|
||||||
|
if (event.key === 'Escape' && isVisible) {
|
||||||
|
$lc.fadeOut()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'ArrowDown' && isVisible) {
|
||||||
|
$lc.focus()
|
||||||
|
$lc.find('li:first-child').addClass('selected')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
$(input).on('keyup', function () {
|
||||||
|
let value = input.value
|
||||||
|
let end = input.selectionEnd
|
||||||
|
let start = value.lastIndexOf("[[", end)
|
||||||
|
let linkEnd = value.lastIndexOf("]]", end - 1)
|
||||||
|
if (start < linkEnd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let query = value.substring(start, end);
|
||||||
|
let results = search.search(query)
|
||||||
|
|
||||||
|
let pos = getCaretCoordinates(input, value.selectionEnd, {})
|
||||||
|
let off = $(input).offset()
|
||||||
|
pos.top += off.top + pos.height
|
||||||
|
pos.left += off.left
|
||||||
|
|
||||||
|
const $lc = $('#link-complete')
|
||||||
|
$lc.offset(pos)
|
||||||
|
|
||||||
|
var template = document.getElementById('link-template').innerHTML;
|
||||||
|
var rendered = Mustache.render(template, {results: results}, {}, ['[[', ']]']);
|
||||||
|
$lc.html(rendered).fadeIn()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
editor.on('stop-editing', function (input) {
|
||||||
|
$('#link-complete').off()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
$.contextMenu({
|
$.contextMenu({
|
||||||
selector: '.marker',
|
selector: '.marker',
|
||||||
items: {
|
items: {
|
||||||
|
|
50
main.go
50
main.go
|
@ -30,9 +30,10 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Backref struct {
|
type Backref struct {
|
||||||
Name string
|
Name string
|
||||||
Title string
|
Title string
|
||||||
Line template.HTML
|
LineHTML template.HTML
|
||||||
|
Line string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page
|
// Page
|
||||||
|
@ -618,6 +619,27 @@ func main() {
|
||||||
mp = NewFilePages("data")
|
mp = NewFilePages("data")
|
||||||
|
|
||||||
http.Handle("/auth/", &authHandler{})
|
http.Handle("/auth/", &authHandler{})
|
||||||
|
http.HandleFunc("/links.json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
type Document struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []Document
|
||||||
|
pages, err := mp.(*FilePages).AllPages()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, page := range pages {
|
||||||
|
results = append(results, Document{page.Title})
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
err = json.NewEncoder(w).Encode(&results)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
http.HandleFunc("/documents.json", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/documents.json", func(w http.ResponseWriter, r *http.Request) {
|
||||||
type Document struct {
|
type Document struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
@ -632,26 +654,36 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, page := range pages {
|
for _, page := range pages {
|
||||||
content := page.Content
|
content := strings.Builder{}
|
||||||
|
|
||||||
var listItems []struct {
|
var listItems []struct {
|
||||||
Indented int
|
Indented int
|
||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(strings.NewReader(content)).Decode(&listItems)
|
err = json.NewDecoder(strings.NewReader(page.Content)).Decode(&listItems)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pageText := ""
|
|
||||||
for _, item := range listItems {
|
for _, item := range listItems {
|
||||||
pageText += strings.Repeat(" ", item.Indented) + "* " + item.Text + "\n"
|
content.WriteString(item.Text)
|
||||||
|
content.WriteByte(' ')
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
content.WriteString(page.Content)
|
||||||
|
content.WriteByte(' ')
|
||||||
|
}
|
||||||
|
|
||||||
content = pageText
|
for page, refs := range page.Refs {
|
||||||
|
content.WriteString(page)
|
||||||
|
content.WriteByte(' ')
|
||||||
|
for _, ref := range refs {
|
||||||
|
content.WriteString(ref.Line)
|
||||||
|
content.WriteByte(' ')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, Document{
|
results = append(results, Document{
|
||||||
Title: page.Title,
|
Title: page.Title,
|
||||||
Body: content,
|
Body: content.String(),
|
||||||
URL: page.Name,
|
URL: page.Name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<li><a href="/{{ $name }}">{{ (index $refs 0).Title }}</a>
|
<li><a href="/{{ $name }}">{{ (index $refs 0).Title }}</a>
|
||||||
<ul>
|
<ul>
|
||||||
{{ range $ref := $refs }}
|
{{ range $ref := $refs }}
|
||||||
<li>{{ $ref.Line }}</li>
|
<li>{{ $ref.LineHTML }}</li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -25,6 +25,24 @@
|
||||||
#autocomplete li > a {
|
#autocomplete li > a {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
#link-complete {
|
||||||
|
z-index: 1;
|
||||||
|
width: 217px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 300px;
|
||||||
|
position: absolute;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#link-complete li {
|
||||||
|
padding: 4px 16px;
|
||||||
|
}
|
||||||
|
#link-complete li.selected {
|
||||||
|
background: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
.monospace {
|
.monospace {
|
||||||
font-family: "Fira Code Retina", monospace;
|
font-family: "Fira Code Retina", monospace;
|
||||||
|
@ -142,6 +160,9 @@
|
||||||
border-bottom-color: #ccc;
|
border-bottom-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background: lightblue;
|
background: lightblue;
|
||||||
|
@ -249,12 +270,13 @@
|
||||||
— created by <a href="https://peterstuifzand.nl/">Peter Stuifzand</a>
|
— created by <a href="https://peterstuifzand.nl/">Peter Stuifzand</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="save-indicator" class="hidden"></div>
|
<div id="save-indicator" class="hide"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="link-complete" class="hide" tabindex="0"></div>
|
||||||
{{ block "footer_scripts" . }}
|
{{ block "footer_scripts" . }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<script src="/public/index.bundle.js"></script>
|
<script src="/public/index.bundle.js"></script>
|
||||||
<div id="result-template" class="hidden">
|
<div id="result-template" class="hide">
|
||||||
<ul>
|
<ul>
|
||||||
[[#results]]
|
[[#results]]
|
||||||
<li><a href="/[[ref]]">[[title]]</a></li>
|
<li><a href="/[[ref]]">[[title]]</a></li>
|
||||||
|
@ -262,5 +284,12 @@
|
||||||
<ll><a href="/edit/[[page]]">Create a page</a></ll>
|
<ll><a href="/edit/[[page]]">Create a page</a></ll>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="link-template" class="hide">
|
||||||
|
<ul>
|
||||||
|
[[#results]]
|
||||||
|
<li>[[item.title]]</li>
|
||||||
|
[[/results]]
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<li><a href="/{{ $name }}">{{ (index $refs 0).Title }}</a>
|
<li><a href="/{{ $name }}">{{ (index $refs 0).Title }}</a>
|
||||||
<ul>
|
<ul>
|
||||||
{{ range $ref := $refs }}
|
{{ range $ref := $refs }}
|
||||||
<li>{{ $ref.Line }}</li>
|
<li>{{ $ref.LineHTML }}</li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user