diff --git a/common/urls.py b/common/urls.py index edbfb41..d4e8f67 100644 --- a/common/urls.py +++ b/common/urls.py @@ -4,4 +4,5 @@ from common import views urlpatterns = [ path("api/draft/save", views.save_draft, name="save_draft"), + path("api/suggestions",views.name_completions,name="name_completions"), ] diff --git a/common/views.py b/common/views.py index 373e0a0..7d43b2e 100644 --- a/common/views.py +++ b/common/views.py @@ -7,7 +7,12 @@ from django.views.generic import TemplateView from ipware import get_client_ip from sentry_sdk import last_event_id +from characters.models import Character from common.models import Draft +from factions.models import Faction +from locations.models import Location +from loot.models import Loot +from notes.models import Note from rpg_notes.secrets import SENTRY_DSN from utils.assets import get_css, get_file_hash @@ -51,6 +56,42 @@ def save_draft(request: HttpRequest) -> HttpResponse: }) +def name_completions(request: HttpRequest) -> HttpResponse: + response_data = [] + + for obj in (list(Location.objects.all()) + + list(Note.objects.all()) + + list(Faction.objects.all()) + + list(Loot.objects.all())): + response_data.append({ + "name": obj.name + }) + if obj.aliases: + for alias in obj.aliases: + response_data.append({ + "name": alias, + "details":obj.name + }) + for char in Character.objects.all(): + response_data.append({ + "name": char.name, + "details": char.subtitle + }) + if char.aliases: + for alias in char.aliases: + response_data.append({ + "name": alias, + "details":char.name + }) + + + response = JsonResponse({ + "suggestions": response_data + }) + response['Cache-Control'] = f'max-age={24 * 60 * 60}' + return response + + @condition(etag_func=calc_etag) def debug_css(request: HttpRequest) -> HttpResponse: css, source_map = get_css(debug=True) diff --git a/static/js/codemirror.ts b/static/js/codemirror.ts index 08a4679..c52d3d7 100644 --- a/static/js/codemirror.ts +++ b/static/js/codemirror.ts @@ -1,6 +1,12 @@ import {EditorView, minimalSetup} from "codemirror" -import {markdownLanguage} from "@codemirror/lang-markdown" +import {markdown, markdownLanguage} from "@codemirror/lang-markdown" import {foldGutter} from "@codemirror/language"; +import { + autocompletion, + Completion, + CompletionContext, + CompletionResult +} from "@codemirror/autocomplete" interface DraftSaveResponse { message: string @@ -25,6 +31,53 @@ ids.forEach(function (id) { // color: "red", // colorLight: "lightred" // }) + + interface Element { + name: string, + details?: string + } + + interface HTTPResponse { + suggestions: Element[] + } + + let names: Element[] = [] + fetch("/api/suggestions", {}) + .then(response => response.json()) + .then((data: HTTPResponse) => { + names = data.suggestions + }) + + function myCompletions(context: CompletionContext): CompletionResult | null { + if (names.length == 0) { + return null + } + let word + if (!context.explicit) { + word = context.matchBefore(/@\w*/) + + } else { + word = context.matchBefore(/\w*/) + } + if (!word) { + return null + } + if (word.from == word.to) + return null + + const options = names.map((s: Element): Completion => ({ + label: "@" + s.name, + apply: s.name, + displayLabel: s.name, + detail: s.details + })) + return { + from: word.from, + options: options + } + } + + const labelEl = element.labels[0] const div = document.createElement("div") element.style.display = "none" @@ -33,7 +86,11 @@ ids.forEach(function (id) { extensions: [ minimalSetup, foldGutter(), - markdownLanguage, + markdown({base: markdownLanguage}), + autocompletion({ + activateOnTyping: true, + override: [myCompletions], + }), EditorView.lineWrapping, EditorView.contentAttributes.of({spellcheck: "true"}), // yCollab(ytext, provider.awareness, {undoManager}) @@ -44,31 +101,31 @@ ids.forEach(function (id) { element.form!.addEventListener("submit", () => { element.value = view.state.doc.toString() }) - // const easyMDE = new EasyMDE({ - // element: element, - // forceSync: true, // for "required" to work - // spellChecker: false, - // nativeSpellcheck: true, - // autoDownloadFontAwesome: false, - // autosave: { - // delay: 1000, - // submit_delay: 5000, - // timeFormat: { - // locale: 'de-AT', - // format: { - // year: 'numeric', - // month: 'long', - // day: '2-digit', - // hour: '2-digit', - // minute: '2-digit', - // second: '2-digit', - // }, - // }, - // }, - // inputStyle: "contenteditable", - // status: ["lines", "words", "cursor", "saveStatus"], - // }); - // window.editor = easyMDE +// const easyMDE = new EasyMDE({ +// element: element, +// forceSync: true, // for "required" to work +// spellChecker: false, +// nativeSpellcheck: true, +// autoDownloadFontAwesome: false, +// autosave: { +// delay: 1000, +// submit_delay: 5000, +// timeFormat: { +// locale: 'de-AT', +// format: { +// year: 'numeric', +// month: 'long', +// day: '2-digit', +// hour: '2-digit', +// minute: '2-digit', +// second: '2-digit', +// }, +// }, +// }, +// inputStyle: "contenteditable", +// status: ["lines", "words", "cursor", "saveStatus"], +// }); +// window.editor = easyMDE const originalLabel = labelEl.innerText setInterval(function () { const content = view.state.doc.toString(); @@ -88,4 +145,5 @@ ids.forEach(function (id) { }) }, 1000 * 30) -}); +}) +; diff --git a/static/main.ts b/static/main.ts index 59df7b6..b99bd28 100644 --- a/static/main.ts +++ b/static/main.ts @@ -3,9 +3,9 @@ import 'vite/modulepreload-polyfill' // import "./scss/main.scss" import "./js/sentry" // @ts-ignore -import {default as Dropdown} from 'bootstrap/js/src/dropdown' +import Dropdown from 'bootstrap/js/src/dropdown' // @ts-ignore -import {default as Collapse} from 'bootstrap/js/src/collapse' +import Collapse from 'bootstrap/js/src/collapse' import "./js/autocomplete" import "./js/popover"