// wikicomplete.js — the `[[` autocomplete source for CodeMirror. // // Wiki links are search queries, so the popup is the same typeahead the header // search box uses: the text typed after `[[` goes to `/_search` and each match // is offered by name. Picking one replaces the query segment with that page's // name — never a path — so what lands in the document is still a query. The // trailing `]]` and any `::display` tail are left untouched. // // Exposes window.WikiComplete.source for main.js to register via CM's // autocompletion(). window.WikiComplete = (function () { // Matches search-suggest.js so both surfaces fire at the same point. var MIN_QUERY_LEN = 2; function fetchSuggestions(query) { return fetch('/_search?q=' + encodeURIComponent(query), { credentials: 'same-origin', headers: { 'Accept': 'application/json' }, }).then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }); } // queryEnd returns where the current query segment ends: the first `]`, the // `::` that starts the display text, or end of line. Replacing only up to // there keeps a re-edit from eating an existing alias or the closing `]]`. function queryEnd(context, pos) { var line = context.state.doc.lineAt(pos); var to = pos; while (to < line.to) { var ch = context.state.sliceDoc(to, to + 1); if (ch === ']') break; if (ch === ':' && context.state.sliceDoc(to, to + 2) === '::') break; to++; } return to; } // CM completion source. Activates on `[[` followed by at least // MIN_QUERY_LEN characters; a cursor past a `::` is in the display tail and // gets nothing. function source(context) { var match = context.matchBefore(/\[\[[^\]\n]*/); if (!match) return null; var content = context.state.sliceDoc(match.from + 2, context.pos); if (content.indexOf('::') !== -1) return null; var query = content.trim(); if (query.length < MIN_QUERY_LEN) return null; var from = match.from + 2; var to = queryEnd(context, context.pos); return new Promise(function (resolve) { if (context.aborted) { resolve(null); return; } fetchSuggestions(query).then(function (resp) { if (context.aborted) { resolve(null); return; } var options = (resp.results || []).map(function (r) { return { label: r.name, detail: '/' + r.path, apply: r.name }; }); // filter:false — the server already ranked these fuzzily, and // CM's own prefix filter would drop the near-misses (typos, // token reorderings) that make the fuzzy search worth having. // No validFor either: every keystroke re-queries from scratch. resolve({ from: from, to: to, options: options, filter: false }); }).catch(function () { resolve(null); }); }); } return { source: source }; })();