This commit is contained in:
th committed 2026-04-07 21:58:45 +02:00
1 parent a9603aa0e8
commit 719f9d51ff
13 files changed
+625 -70

No files matched your search

+76 -3
View File
@@ -16,8 +16,10 @@ const inpColor = $('inp-color');
const inpBucket = $('inp-bucket');
const presets = $('color-presets');
const linkAc = $('link-ac');
const tagAc = $('tag-ac');
let linkAcVisible = false, linkAcItems = [], linkAcActive = 0;
let tagAcVisible = false, tagAcItems = [], tagAcActive = 0;
let morphGhost = null, morphTimer = null;
function clearMorphGhost() {
@@ -112,6 +114,8 @@ CARD_COLORS.forEach(c => {
function selectColor(hex) {
inpColor.value = hex;
const bar = $('editor-color');
if (bar) bar.style.background = hex;
presets.querySelectorAll('.color-swatch').forEach(el => {
const a = el.dataset.color === hex;
el.classList.toggle('border-t-bright', a);
@@ -450,11 +454,69 @@ function completeLinkAc(idx) {
}
body.addEventListener('input', () => {
const info = getLinkQuery();
info ? showLinkAc(info.query) : hideLinkAc();
const linkInfo = getLinkQuery();
linkInfo ? showLinkAc(linkInfo.query) : hideLinkAc();
const tagInfo = getTagQuery();
tagInfo ? showTagAc(tagInfo.query) : hideTagAc();
updatePreview();
});
// ── Tag autocomplete (#) ──
function getTagQuery() {
const before = body.value.substring(0, body.selectionStart);
// Find # preceded by whitespace or start of line, not part of heading (# + space)
const m = before.match(/(?:^|[\s])#([a-zA-Z0-9_\/-]*)$/m);
if (!m) return null;
const start = before.length - m[0].length + (m[0].startsWith('#') ? 0 : 1); // skip leading whitespace
return { start, query: m[1] };
}
function getAllTags() {
const set = new Set();
for (const c of state.cards) {
for (const t of (c.tags || '').split(',')) { if (t) set.add(t); }
}
return [...set].sort();
}
function showTagAc(query) {
const q = query.toLowerCase();
const all = getAllTags();
tagAcItems = all.filter(t => !q || t.includes(q));
if (!tagAcItems.length) { hideTagAc(); return; }
tagAcActive = 0;
tagAc.innerHTML = tagAcItems.slice(0, 8).map((t, i) => `
<div class="ac-item ${i === 0 ? 'active' : ''}" data-idx="${i}">
<span class="ac-title font-pixel">#${esc(t)}</span>
</div>`).join('');
tagAcItems = tagAcItems.slice(0, 8);
tagAc.querySelectorAll('.ac-item').forEach(el => {
el.addEventListener('mousedown', e => { e.preventDefault(); completeTagAc(parseInt(el.dataset.idx)); });
});
const rect = body.getBoundingClientRect();
const lines = body.value.substring(0, body.selectionStart).split('\n');
tagAc.style.top = Math.min(rect.top + (lines.length - 1) * 22 - body.scrollTop + 26, innerHeight - 200) + 'px';
tagAc.style.left = Math.min(rect.left + 40, innerWidth - 320) + 'px';
tagAc.classList.remove('hidden');
tagAcVisible = true;
}
function hideTagAc() { tagAc.classList.add('hidden'); tagAcVisible = false; }
function completeTagAc(idx) {
const tag = tagAcItems[idx];
if (!tag) return;
const info = getTagQuery();
if (!info) { hideTagAc(); return; }
const before = body.value.substring(0, info.start);
const after = body.value.substring(body.selectionStart);
const ins = `#${tag} `;
body.value = before + ins + after;
body.selectionStart = body.selectionEnd = info.start + ins.length;
hideTagAc(); updatePreview(); body.focus();
}
// ── Line helpers ──
function getLineInfo() {
@@ -511,6 +573,17 @@ body.addEventListener('keydown', e => {
linkAc.querySelectorAll('.ac-item').forEach((el, i) => el.classList.toggle('active', i === linkAcActive));
return;
}
if (tagAcVisible) {
if (e.key === 'ArrowDown') { e.preventDefault(); tagAcActive = Math.min(tagAcActive + 1, tagAcItems.length - 1); }
else if (e.key === 'ArrowUp') { e.preventDefault(); tagAcActive = Math.max(tagAcActive - 1, 0); }
else if (e.key === 'Enter' || e.key === 'Tab') { e.preventDefault(); completeTagAc(tagAcActive); return; }
else if (e.key === 'Escape') { e.preventDefault(); hideTagAc(); return; }
tagAc.querySelectorAll('.ac-item').forEach((el, i) => {
el.classList.toggle('active', i === tagAcActive);
if (i === tagAcActive) el.scrollIntoView({ block: 'nearest' });
});
return;
}
// Alt+Arrow: move line
if (e.altKey && !e.shiftKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
e.preventDefault();
@@ -526,7 +599,7 @@ body.addEventListener('keydown', e => {
if (e.key === 'Tab') { e.preventDefault(); insertMd(' '); }
});
body.addEventListener('blur', () => setTimeout(hideLinkAc, 150));
body.addEventListener('blur', () => { setTimeout(hideLinkAc, 150); setTimeout(hideTagAc, 150); });
// ── File upload (paste + drag-drop) ──