fix: verstak:// links in preview, case-insensitive search, keyboard layout swap
1. Fix verstak:// links rendered as blocked/strikethrough in markdown preview: - Changed href from 'javascript:void(0)' to hash-based '#verstak-type-id' - DOMPurify no longer strips the link; click handler uses data-verstak-href - CSS already handles .md-link--internal with cyan color, no strikethrough 2. Add markdown label escaping for internal link picker: - New escapeMarkdownLabel() in markdown.ts escapes [ ] ( ) - Applied in InternalLinkPicker.selectResult() before inserting markdown 3. Fix case-insensitive search for RU/EN: - Add title_lower column (migration 019) populated by Go strings.ToLower - BackfillTitleLower() runs after migrations to populate existing rows - Search() now queries title_lower with Go-level lowercase (Unicode-aware) - insertNode() and UpdateTitle() populate title_lower automatically - New migration 019 + BackfillTitleLower in storage.go - Tests: TestSearchCaseInsensitive, TestSearchFindsCreatedNode 4. Add keyboard layout swap search support: - New keyboardLayout.ts utility with RU↔EN QWERTY mapping - expandKeyboardVariants() generates original + swapped + lowercased variants - InternalLinkPicker.search() queries all variants in parallel, deduplicates by ID - Examples: dthcnfr → верстак, руддщ → hello Files changed: - markdown.ts: hash href + escapeMarkdownLabel export - InternalLinkPicker.svelte: label escaping + layout swap search - keyboardLayout.ts: new RU/EN layout swap utility - repository.go: title_lower in Search/insertNode/UpdateTitle - storage.go: migration019 + BackfillTitleLower - migrations_019.sql.go: new migration - search_test.go, repository_test.go: new tests
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { escapeMarkdownLabel } from '../../markdown/markdown';
|
||||
import { expandKeyboardVariants } from '../../util/keyboardLayout';
|
||||
|
||||
export let visible = false;
|
||||
|
||||
@@ -23,14 +25,9 @@
|
||||
|
||||
// Map node types from backend to our filter types
|
||||
function nodeTypeToFilter(type) {
|
||||
// case, project, client, document, recipe, space → case
|
||||
// note → note
|
||||
// file → file
|
||||
// secret → secret
|
||||
if (type === 'note') return 'note';
|
||||
if (type === 'file') return 'file';
|
||||
if (type === 'secret') return 'secret';
|
||||
// Everything else goes to "case"
|
||||
return 'case';
|
||||
}
|
||||
|
||||
@@ -42,9 +39,32 @@
|
||||
loading = true;
|
||||
error = '';
|
||||
try {
|
||||
const res = await window['go']['main']['App']['SearchNodes'](query.trim()) || [];
|
||||
// Expand query with keyboard layout variants for tolerant search
|
||||
const variants = expandKeyboardVariants(query.trim());
|
||||
// Deduplicate: skip variants identical to the original query's lowercase
|
||||
const seen = new Set();
|
||||
const queries = [];
|
||||
for (const v of variants) {
|
||||
if (!seen.has(v)) {
|
||||
seen.add(v);
|
||||
queries.push(v);
|
||||
}
|
||||
}
|
||||
// Execute all variant queries in parallel
|
||||
const allResults = await Promise.all(
|
||||
queries.map(q => window['go']['main']['App']['SearchNodes'](q).catch(() => []))
|
||||
);
|
||||
// Merge and deduplicate by node ID
|
||||
const merged = new Map();
|
||||
for (const arr of allResults) {
|
||||
for (const n of (arr || [])) {
|
||||
if (!merged.has(n.id)) {
|
||||
merged.set(n.id, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Filter by active type
|
||||
results = res.filter(n => nodeTypeToFilter(n.type) === activeType);
|
||||
results = Array.from(merged.values()).filter(n => nodeTypeToFilter(n.type) === activeType);
|
||||
selectedIndex = 0;
|
||||
} catch (e) {
|
||||
error = String(e);
|
||||
@@ -77,7 +97,8 @@
|
||||
}
|
||||
|
||||
function selectResult(item) {
|
||||
const md = `[${item.title}](verstak://${nodeTypeToFilter(item.type)}/${item.id})`;
|
||||
const label = escapeMarkdownLabel(item.title || item.id);
|
||||
const md = `[${label}](verstak://${nodeTypeToFilter(item.type)}/${item.id})`;
|
||||
dispatch('insert', { markdown: md });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user