From 46b63b56e82870f88cf75c70043871e9f6329a24 Mon Sep 17 00:00:00 2001 From: beo3000 Date: Sun, 1 Mar 2026 10:18:55 +0100 Subject: [PATCH] upd mention-funktion --- ka-note/VERSION | 2 +- ka-note/client/src/lib/actions/mention.ts | 9 ++- .../src/lib/components/DashboardView.svelte | 65 ++++++++++-------- .../lib/components/RenderedMarkdown.svelte | 9 ++- ka-note/client/src/lib/utils/extractors.ts | 2 +- .../client/src/lib/utils/mentionReplace.ts | 4 +- .../client/src/lib/utils/renderMarkdown.ts | 31 +++++++-- ka-note/server/ka-note.db-shm | Bin 32768 -> 32768 bytes ka-note/server/ka-note.db-wal | Bin 4194192 -> 4194192 bytes 9 files changed, 77 insertions(+), 45 deletions(-) diff --git a/ka-note/VERSION b/ka-note/VERSION index ccd7ac1..e0f9632 100644 --- a/ka-note/VERSION +++ b/ka-note/VERSION @@ -1 +1 @@ -1.1.106 \ No newline at end of file +1.1.108 \ No newline at end of file diff --git a/ka-note/client/src/lib/actions/mention.ts b/ka-note/client/src/lib/actions/mention.ts index 63719fe..fa93571 100644 --- a/ka-note/client/src/lib/actions/mention.ts +++ b/ka-note/client/src/lib/actions/mention.ts @@ -28,7 +28,7 @@ export function mention(node: HTMLInputElement | HTMLTextAreaElement) { for (let i = pos - 1; i >= 0; i--) { const ch = text[i]; if (ch === '@') { - if (i === 0 || /\s/.test(text[i - 1])) { + if (i === 0 || /[\s\n]/.test(text[i - 1])) { mentionStart = i; mentionMode = '@'; return { query: text.slice(i + 1, pos), mode: '@' }; @@ -45,10 +45,9 @@ export function mention(node: HTMLInputElement | HTMLTextAreaElement) { } return null; } - // Allow spaces only when we haven't yet passed a non-word char other than space - // (needed for "-> ANDST" where the space is between trigger and query). - // For @-mentions, spaces are not allowed in the query (quoted names use autocomplete). - if (/\s/.test(ch)) return null; + // For ->, a single space between trigger and query is valid — skip it. + if (ch === ' ' && i > 1 && text[i - 1] === '>' && text[i - 2] === '-') continue; + if (/[\s\n]/.test(ch)) return null; } return null; } diff --git a/ka-note/client/src/lib/components/DashboardView.svelte b/ka-note/client/src/lib/components/DashboardView.svelte index d6d45ef..00a9a39 100644 --- a/ka-note/client/src/lib/components/DashboardView.svelte +++ b/ka-note/client/src/lib/components/DashboardView.svelte @@ -39,6 +39,7 @@ // Metadata collapsed by default let metaOpen = $state(false); + let abbrError = $state(''); // --- Notes --- let noteTitle = $state(''); @@ -424,13 +425,25 @@ { e.currentTarget.value = e.currentTarget.value.replace(/\s/g, '').toUpperCase(); }} - onchange={(e) => { - const v = e.currentTarget.value.replace(/\s/g, '').toUpperCase(); - updateMeta('abbreviation', v || undefined); + placeholder="z.B. CHfi" + oninput={(e) => { e.currentTarget.value = e.currentTarget.value.replace(/\s/g, ''); }} + onchange={async (e) => { + const v = e.currentTarget.value.replace(/\s/g, ''); + if (!v) { updateMeta('abbreviation', undefined as any); return; } + const conflict = await db.contexts + .filter(c => !c.deletedAt && c.type === 'person' && c.id !== context.id + && (c.meta as PersonMeta | null)?.abbreviation?.toLowerCase() === v.toLowerCase()) + .first(); + if (conflict) { + abbrError = `Kürzel bereits vergeben (${conflict.name.replace(/^Person\s+/, '')})`; + e.currentTarget.value = meta.abbreviation ?? ''; + } else { + abbrError = ''; + updateMeta('abbreviation', v); + } }} /> + {#if abbrError}{abbrError}{/if}
@@ -507,30 +520,26 @@ {@const lines = entry.text.split('\n')} {@const title = lines[0]} {@const body = lines.slice(1).join('\n').trim()} - {@const isDone = !!entry.doneAt} -
- - {entry.date} {formatTime(entry.updatedAt)} -
-
- {#if body} - - {/if} +
+
+ {entry.date} {formatTime(entry.updatedAt)} +
+ + +
- - +
+ {#if body} + + {/if}
{/if} {/each} diff --git a/ka-note/client/src/lib/components/RenderedMarkdown.svelte b/ka-note/client/src/lib/components/RenderedMarkdown.svelte index a385b9c..36f2b4b 100644 --- a/ka-note/client/src/lib/components/RenderedMarkdown.svelte +++ b/ka-note/client/src/lib/components/RenderedMarkdown.svelte @@ -38,7 +38,8 @@ function markUnknown(chip: HTMLElement, prefix: string) { // Replace the chip (and its preceding arrow span) with plain text const arrow = chip.previousElementSibling; - const text = document.createTextNode(`${prefix}${chip.dataset.assignment ?? chip.dataset.person ?? ''}`); + const name = chip.dataset.assignment ?? chip.dataset.person ?? ''; + const text = document.createTextNode(`${prefix}${name}`); chip.parentNode?.insertBefore(text, chip); chip.remove(); if (arrow && /→|→/.test(arrow.textContent ?? '')) arrow.remove(); @@ -49,7 +50,11 @@ await Promise.all([ ...Array.from(assignmentChips).map(async chip => { const ctx = await findContextByAbbreviation(chip.dataset.assignment!); - if (!ctx) { markUnknown(chip, '-> '); return; } + if (!ctx) { + const showArrow = chip.dataset.showArrow === '1'; + markUnknown(chip, showArrow ? '=> ' : '-> '); + return; + } const subType = (ctx.meta as Record | null)?.personSubType as PersonSubType | undefined; applySubtypeColor(chip, subType); }), diff --git a/ka-note/client/src/lib/utils/extractors.ts b/ka-note/client/src/lib/utils/extractors.ts index e0df826..7d6b1b2 100644 --- a/ka-note/client/src/lib/utils/extractors.ts +++ b/ka-note/client/src/lib/utils/extractors.ts @@ -1,5 +1,5 @@ export function extractAssignments(text: string): string[] { - const regex = /->\s*([\w]+)/g; + const regex = /(?:->|=>)\s*([\w]+)/g; const result: string[] = []; let match; while ((match = regex.exec(text)) !== null) { diff --git a/ka-note/client/src/lib/utils/mentionReplace.ts b/ka-note/client/src/lib/utils/mentionReplace.ts index 887afb5..50eb393 100644 --- a/ka-note/client/src/lib/utils/mentionReplace.ts +++ b/ka-note/client/src/lib/utils/mentionReplace.ts @@ -43,9 +43,9 @@ export function renameMentions( return text; } -/** Replace -> OldAbbr with -> NewAbbr in text (exact word match). */ +/** Replace -> OldAbbr / => OldAbbr with -> NewAbbr / => NewAbbr in text (case-insensitive word match). */ export function renameAssignment(text: string, oldAbbr: string, newAbbr: string): string { const esc = escapeRegex(oldAbbr); - const pattern = new RegExp(`(->\\s*)${esc}(?=[^\\w]|$)`, 'g'); + const pattern = new RegExp(`((?:->|=>)\\s*)${esc}(?=[^\\w]|$)`, 'gi'); return text.replace(pattern, `$1${newAbbr}`); } diff --git a/ka-note/client/src/lib/utils/renderMarkdown.ts b/ka-note/client/src/lib/utils/renderMarkdown.ts index 965cb01..c8008da 100644 --- a/ka-note/client/src/lib/utils/renderMarkdown.ts +++ b/ka-note/client/src/lib/utils/renderMarkdown.ts @@ -4,6 +4,23 @@ import hljs from 'highlight.js'; // --- Custom inline extensions for Ka-Note tags --- +const assignmentWithArrowExt: TokenizerExtension & RendererExtension = { + name: 'assignmentWithArrow', + level: 'inline', + start(src: string) { return src.indexOf('=>'); }, + tokenizer(src: string) { + // \u200B may be prepended during preprocessing. + const match = /^\u200B?=>\s*"([^"]+)"/.exec(src) ?? /^\u200B?=>\s*([\w-]+)/.exec(src); + if (match) { + return { type: 'assignmentWithArrow', raw: match[0], name: match[1] }; + } + }, + renderer(token) { + return '' + + `${token.name}`; + } +}; + const assignmentExt: TokenizerExtension & RendererExtension = { name: 'assignment', level: 'inline', @@ -17,8 +34,7 @@ const assignmentExt: TokenizerExtension & RendererExtension = { } }, renderer(token) { - return '' - + `${token.name}`; + return `${token.name}`; } }; @@ -108,7 +124,7 @@ function truncateLinkUrl(url: string): string { const markedInstance = new Marked({ gfm: true, breaks: true, - extensions: [wikiLinkExt, assignmentExt, projectRefExt, companyRefExt, personMentionExt], + extensions: [wikiLinkExt, assignmentWithArrowExt, assignmentExt, projectRefExt, companyRefExt, personMentionExt], renderer: { link({ href, title, text }) { if (!href) return text; @@ -189,6 +205,7 @@ const PURIFY_CONFIG = { ALLOWED_ATTR: [ 'href', 'target', 'rel', 'src', 'alt', 'title', 'width', 'height', 'class', 'style', 'data-person', 'data-project', 'data-company', 'data-wiki-page', + 'data-assignment', 'data-show-arrow', 'type', 'checked', 'disabled' // for GFM task lists ], ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|blob):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i @@ -202,9 +219,11 @@ export function renderMarkdown(text: string): string { if (!text) return ''; // tiptap-markdown escapes [ as \[ — restore [[WikiLinks]] before parsing const unescaped = text.replace(/\\\[\\\[(.+?)\\\]\\\]/g, '[[$1]]').replace(/\\?-(?:>|>)/g, '->'); - // Prevent Marked from treating -> at line start as a list item + blockquote. - // Replace leading -> with a zero-width space prefix so it stays inline. - const preprocessed = unescaped.replace(/(^|\n)([ \t]*)->/g, '$1$2\u200B->'); + // Prevent Marked from treating -> / => at line start as a list item + blockquote. + // Replace leading -> / => with a zero-width space prefix so they stay inline. + const preprocessed = unescaped + .replace(/(^|\n)([ \t]*)->/g, '$1$2\u200B->') + .replace(/(^|\n)([ \t]*)=>/g, '$1$2\u200B=>'); const raw = markedInstance.parse(preprocessed) as string; const withTables = wrapTables(raw); const withCallouts = processCallouts(withTables); diff --git a/ka-note/server/ka-note.db-shm b/ka-note/server/ka-note.db-shm index c41327bb119b9567e167f0722bafaffda342d3be..0383b4ba848935cab557f4de4415bf1e1fd078ba 100644 GIT binary patch delta 1111 zcmb7?TWnNS7=_PTGG~6Ul*3I5j0#$$3e+;tf?A~@j+Lv>3Y1$*%d|tGWvJ6jdUFy; zH6)VYe$X^N01_hCfJa}NCcHo?qTG~=SOgKdc`Kk;tF)XwGl@-nawq>ed$0fd*V+#| z9A-Gow-T<5GxVZUFbu28+0;3k;nf>IafOa`jJ&kBSIybV9d)@oZoJ(zY19AZBj(Ph z)bZ`^v0}R0n5mPE&AZYLG`E@RsyViAffHZoRFP8wHI-Q^==Qksos(Z(G)E83b9O77 z(%ed?{>$b%YQAP&5mt@oK>X{ZyM?-|wzv{{=-RW;-t4}csTZGR znl`{Xj2pOv!F-(4IE!m_$AlF!=Aj%~T@HK6=A6BiAg9g~RjJ|xH zMbQYAu}tJRIx(8rT4{v3wcIP;Bc3lX5M|;5*oG7M8%eCtN-L~-V+r2DXSjlXOy{Z? zmx*$Ggx~Ne--vR`@{9Zci!ZWRU+P2E>~D~h_yL}6_*lym( zGhC>FkD=mLi~NWIoWeEH9F?{v-E7AgF4E@!)a4SdT*E+SutZz5t3GR!5xB9ZeV&pGzGBfs@V9^OS0u453VvNRUrElZTs=*C#)XeAr*e!Pc+_!&d# z4KX~A8L z;B3t#spKk=uK^}=NyNq8SLF=-f?<4>q1e#*>U24R-!YPNs5~_Zat7qHTpAtNQ{z0t cR{Vh{I9Dq^tIU{-TI|GGJj4_((=IJ|0O))-;Q#;t delta 673 zcmb7BO(;ZB6u#$So?a4G77`^{plOPQEMzfFieXG7KiN!8exk|GV;)}7WHW`c7|lxP z<#(cs#WZDOA**2_X+$=Y{P9jGh1mBzvL3*d_E<3QG zNggAupu;X>Pter{yTp@N)R_6hTxkk%CU2Ak40yUsMnS8^FsRd;;tUm|Z3!VOq|fbU z0~MD$PKCS%WQ}w{2q^c)yc$^7ZIc5bkf%u}%)_Cj%5cFbtO=M6 zpX0N+0$5@aJ^%m! diff --git a/ka-note/server/ka-note.db-wal b/ka-note/server/ka-note.db-wal index 7ad21e584a61e5f500d3fc3ef26dc3a718fcac8d..7600343f04e341a12686209446837d0f4528af2a 100644 GIT binary patch delta 4014 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 IYo=lS9~fKE_W%F@ delta 3558 zcmaKv3pmtSAIA-0T*k1NLc39G2Q$ndmq92sYE`^$TTvvph*gqGZtdTady0RhP;QYj zT{xsR8})9xb{m!wC9I~ch)6=^J*PRl?`+Teyq@RlncwsO{?Gq%e&6#s|J=DbN-k6q zK_R3G8DbtGOUM!O1eH)A6bU6lnV=CW1f5VN)QHarbwY#CBpAeeVgaE=XcIbD79I>^ zP=ci3pWfs1GG*&>mcCw-Sz57qdlAsJ=Blbv)i=BTX1sm(&-J^FVO7{Gy~IfL(*iPy zC#OR(Cza_&P84(CC@>PxsW*0=-Z9#hwF88Pa#iT$Z!=60Wh*&y*GPwI*EJK=w zP!$@%VRAZrrxx`#I)kWj#uS~*ImXqZW{DCEmq)GG3LM9frElkk%k7*lcpGd3SU$2! z4l7PQ!UX*d=&mMOnima!a6SVBks30p zU2JFx-(f(bN;$Ok)<#bS|X#Z>tv<8On@L0S%}K9 zKg}}hZq?^FK*ujN(Bbj)Ehk04&V$SqXmSAkyukpS+C`%^?e!a-1Y>Gwq}ev;bK26N zY1_WC9H{<^M%uu#@$3HJE(e-lv7m)D0xg>cjtG-KYgGk~BgnGa#7p$>bz3gTZD+wU zWUJ8h+Xu`SAc|%P%bHG>{p$-qHIYI$mLWKFk<~*dGmH&s)bGEqjGt5G&w&zSf=Kbkv!?FZ`iTtGqrE z88Rd4CtqsZNeA#+(4mtJ9cM&6-RWz}2Y}-=S4LHHvt-j092Q(z&t}$x5-#$)T!8-9 zWkVf$LxN@?^ya`J?-kwC7QCp41=cQRunfGV#J;|WTTHt7m@T4gDM**|0j0xcX>`r4 zk^?KyEciLAo}K`@^R?l?;c;d(s+#s>-h6|p+aM_pjdVYjIkms&Vc91==|)FAscBoW z{y5qB;zfMpeq3N3oMVH`rL8s0sigoh52v$V$)Wp4p$S6C*x-yd&`E^q_Fjp*n@jf&rg z16Az)Zu+NmD)p$JQOu7nbM^a?gIjdgF_3T3ghOLVBT{nb2K4~<1Ug!xr%>yoIBteg z2g{e<&o3$%WCsKPtLSJ2z4HA!6J<{nTUg#{>~Hw7dc8=T>>E9%FxeFvaf!>r)1JTu zKuMMMafS51R=4%(h7DPuc?Jzqy)E0mORxJ5T18;_`4&_8<9)GoFs6;Be>N|dXuebE zjFur-ex=b*s{!`$K-U%>HBDWtUaB4$irV|H;XBJ^c|!pzzNDipZxMMnkL?SN6!1O` zMr%jn9hqtAx1j$LnlT@J9*xTLG`i}p5PcU!>GR;=)4k#3bQnMRlfDuMBSd>~FffVn z1|_SIw|lof{B1=iOs0TYu<AB1nvt@|CEZ6Znvo=Y# zL_U#^II<}$7_;TeElLjF!td|ln0zbj^L9-=myFpm+Xv>(L@uEG9kajKQj>EntWf5u z7iNp@zZ*|4V~3EEY#C=f2pi(mpF@OKi$W(b5v5zcAemUqoEPLBxVEu*06rB=6>W&#L zpkByMci;_dO2gTraIB6`Y~M`5+2RkKNm8c#xd&&<-Zs1S-5-n*luq`T=->7oyS!^3PP{$jKAXmsWUqK zII5TVbG4W{_N2m_=)LmeI8OI^e3iPFCwF4%%-h)JVz z-d}51h^ez6zR`YSG3S{ib(Esb>YldAcuG*m!YH=#z?nzgm^%Adxvwve4eDa*WM4a$ zWf`a&fT_a?xotC}kj}@{NfI`AI5pflhL$e~i^4oMjhdV6J{`c$T1yH&jJ2gH&SYC2 z+fNO}1AY#mosHC4^qYKS%CG>u8-}iQs{K)y@;Vp0UhytL^B$ z_Va-%@gD4->%)K->G_f~uMy!E;|uOUKW^)SKjQ2Eypt95)NM^nC<7=%C?hBq6dTGI s$^?o7#f373GJ`UQS_)+WWeH^kWevrHvVpRNvV&R%We>JBIT)|}4-%JOm;e9(