feat(noter): nævn en person i en note, og den er delt med dem
At skrive nogens navn i en note *er* at ville have dem til at læse den. Indtil nu var det to handlinger — skriv navnet, åbn panelet, find personen, del — og den anden blev glemt, så noten stod med en besked til en, der ikke kunne se den. Nu deler `@Navn` noten med den, der bliver nævnt, og siger det til dem. Editoren foreslår personer efter `@`, ligesom `#` foreslår projekter. Slået op i navnene frem for tolket ud af teksten. Et navn kan have mellemrum i sig, og et udtryk, der skulle fange dem alle, ville også fange sætningen bagefter: `@Sofie i morgen` ville blive til en person, der hed noget i den retning. Her er der ikke noget at gætte — der står et navn, eller der gør ikke. Det er også dét, der gør det forsvarligt at dele på: adgang må ikke afhænge af, hvor langt et regulært udtryk valgte at læse. En mailadresse er ikke en omtale, det længste navn vinder, og ejefaldets s hører til sætningen. Kun nye omtaler, sammenlignet med teksten før gemningen på samme måde som links: en note gemmes ved hver tastepause, så uden sammenligningen var hver gemning en ny deling og en ny besked om det samme navn. Kun ejerens omtaler deler. En medredaktør må skrive i noten, men ikke give den videre — ellers kunne den, ejeren har delt med, skrive et navn og dermed dele ejerens note med en, ejeren aldrig har hørt om. Det er den samme regel, panelet følger. Læser, ikke medredaktør: en omtale siger "se på det her", og det mindste, der opfylder sætningen, er det rigtige at give. En rolle, ejeren har hævet i panelet, sænkes ikke igen af en omtale. Og én besked pr. handling: den, der lige er blevet nævnt, får "nævnte dig" og ikke også "rettede en note" om den samme gemning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8ee9fbedfb
commit
d8f5f6e236
@@ -20,7 +20,10 @@
|
||||
import { api, humanMessage } from '$lib/api.js';
|
||||
import { colorVar } from '$lib/colors.js';
|
||||
|
||||
let { note, notes = [], onchange, onsave, onopennote } = $props();
|
||||
// `people` er dem, et `@` kan betyde: dem, noten kan deles med. Givet ind frem
|
||||
// for hentet her, fordi siden allerede har listen — den står i delepanelet — og
|
||||
// to hentninger af den samme liste er to, der kan blive uenige.
|
||||
let { note, notes = [], people = [], onchange, onsave, onopennote } = $props();
|
||||
|
||||
let editor;
|
||||
// Forslagslisten, så tastaturvalget kan rulle den frem.
|
||||
@@ -87,6 +90,7 @@
|
||||
const SYNTAX = [
|
||||
{ mark: '#', what: 'notes.syntaxProject' },
|
||||
{ mark: '[[', what: 'notes.syntaxNote' },
|
||||
{ mark: '@', what: 'notes.syntaxPerson' },
|
||||
{ mark: '⌘B', what: 'notes.bold' },
|
||||
{ mark: '⌘U', what: 'notes.underline' }
|
||||
];
|
||||
@@ -509,6 +513,19 @@
|
||||
return { kind: 'note', node, start: selection.anchorOffset - note[1].length, term: note[1] };
|
||||
}
|
||||
|
||||
// Et navn kan have et mellemrum i sig, så ordet efter `@` må tage ét med:
|
||||
// "Sofie J" skal kunne snævre listen ind til Sofie Jensen. Mere end ét ville
|
||||
// læse sætningen efter navnet med.
|
||||
const person = /(?:^|\s)@([\p{L}\p{N}._-]*(?:\s[\p{L}\p{N}._-]*)?)$/u.exec(before);
|
||||
if (person) {
|
||||
return {
|
||||
kind: 'person',
|
||||
node,
|
||||
start: selection.anchorOffset - person[1].length,
|
||||
term: person[1]
|
||||
};
|
||||
}
|
||||
|
||||
const project = /(?:^|\s)#([\p{L}\p{N}_-]*)$/u.exec(before);
|
||||
if (project) {
|
||||
return {
|
||||
@@ -562,7 +579,12 @@
|
||||
// pixels for hvert bogstav, mens listen snævres ind.
|
||||
if (!suggestions.length) menuAt = tagPosition(partial);
|
||||
|
||||
suggestions = partial.kind === 'note' ? noteMatches(term) : projectMatches(term);
|
||||
suggestions =
|
||||
partial.kind === 'note'
|
||||
? noteMatches(term)
|
||||
: partial.kind === 'person'
|
||||
? personMatches(term)
|
||||
: projectMatches(term);
|
||||
chosen = 0;
|
||||
}
|
||||
|
||||
@@ -586,6 +608,16 @@
|
||||
.sort((a, b) => rank(a.label, b.label, term));
|
||||
}
|
||||
|
||||
// Dem, noten kan deles med. Ingen filtrering på, hvem der allerede har den: at
|
||||
// nævne en, der læser med i forvejen, er en almindelig ting at gøre — og
|
||||
// omtalen giver dem besked, selv om den ikke giver dem noget nyt.
|
||||
function personMatches(term) {
|
||||
return people
|
||||
.filter((p) => p.name && p.name.toLowerCase().includes(term))
|
||||
.map((p) => ({ id: p.id, kind: 'person', label: p.name, color: p.avatar_color }))
|
||||
.sort((a, b) => rank(a.label, b.label, term));
|
||||
}
|
||||
|
||||
function rank(a, b, term) {
|
||||
const al = a.toLowerCase();
|
||||
const bl = b.toLowerCase();
|
||||
@@ -1011,6 +1043,10 @@
|
||||
{#if item.kind === 'note'}
|
||||
<span class="dot note" aria-hidden="true">[[</span>
|
||||
{item.label}
|
||||
{:else if item.kind === 'person'}
|
||||
<span class="dot" style="background: {colorVar(item.color)}" aria-hidden="true"
|
||||
></span>
|
||||
@{item.label}
|
||||
{:else}
|
||||
<span class="dot" style="background: {colorVar(item.color)}" aria-hidden="true"
|
||||
></span>
|
||||
|
||||
Reference in New Issue
Block a user