feat: Notizen — automatische Textkontrastfarbe + Formatierungs-Toolbar
Kontrastproblem: Titel, Inhalt und Footer-Elemente der Notizkarten überschrieben die Inline-Textfarbe mit festen Token-Werten. Jetzt erben alle Elemente die adaptiv berechnete Farbe (dunkel auf hellen Karten, hell auf dunklen). Formatierungs-Toolbar: Fett, Kursiv und Liste als Buttons über dem Textfeld im Editor. Fügt Markdown-Syntax ein, unterstützt Selektion und Tastaturkürzel (Strg+B, Strg+I). Markdown-Rendering bleibt unverändert. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+76
-1
@@ -149,6 +149,53 @@ function renderNoteCard(note) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------
|
||||||
|
// Formatierungs-Helfer
|
||||||
|
// --------------------------------------------------------
|
||||||
|
|
||||||
|
function applyFormat(textarea, format) {
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
const text = textarea.value;
|
||||||
|
const sel = text.slice(start, end);
|
||||||
|
|
||||||
|
let before, after, insert;
|
||||||
|
switch (format) {
|
||||||
|
case 'bold':
|
||||||
|
before = '**'; after = '**';
|
||||||
|
insert = sel || 'Text';
|
||||||
|
break;
|
||||||
|
case 'italic':
|
||||||
|
before = '*'; after = '*';
|
||||||
|
insert = sel || 'Text';
|
||||||
|
break;
|
||||||
|
case 'list': {
|
||||||
|
// Jede Zeile mit "- " prefixen oder neuen Listenpunkt einfügen
|
||||||
|
if (sel) {
|
||||||
|
const lines = sel.split('\n').map((l) => l.startsWith('- ') ? l : `- ${l}`);
|
||||||
|
textarea.setRangeText(lines.join('\n'), start, end, 'end');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
before = ''; after = '';
|
||||||
|
const lineStart = text.lastIndexOf('\n', start - 1) + 1;
|
||||||
|
const currentLine = text.slice(lineStart, start);
|
||||||
|
if (currentLine.trim() === '') {
|
||||||
|
textarea.setRangeText('- ', start, start, 'end');
|
||||||
|
} else {
|
||||||
|
textarea.setRangeText('\n- ', start, start, 'end');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const replacement = `${before}${insert}${after}`;
|
||||||
|
textarea.setRangeText(replacement, start, end, 'select');
|
||||||
|
// Selektion auf den eingefügten Text setzen (ohne Marker)
|
||||||
|
textarea.selectionStart = start + before.length;
|
||||||
|
textarea.selectionEnd = start + before.length + insert.length;
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
// Modal
|
// Modal
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
@@ -165,8 +212,20 @@ function openNoteModal({ mode, note = null }) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label" for="note-content">Inhalt *</label>
|
<label class="form-label" for="note-content">Inhalt *</label>
|
||||||
|
<div class="note-format-toolbar">
|
||||||
|
<button type="button" class="note-format-btn" data-format="bold" title="Fett (Strg+B)">
|
||||||
|
<i data-lucide="bold" style="width:14px;height:14px;" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="note-format-btn" data-format="italic" title="Kursiv (Strg+I)">
|
||||||
|
<i data-lucide="italic" style="width:14px;height:14px;" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<span class="note-format-btn--sep"></span>
|
||||||
|
<button type="button" class="note-format-btn" data-format="list" title="Liste">
|
||||||
|
<i data-lucide="list" style="width:14px;height:14px;" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<textarea class="form-input" id="note-content" rows="6"
|
<textarea class="form-input" id="note-content" rows="6"
|
||||||
placeholder="Notiz eingeben… (** fett **, * kursiv *, - Liste)"
|
placeholder="Notiz eingeben…"
|
||||||
style="resize:vertical;">${escHtml(isEdit ? note.content : '')}</textarea>
|
style="resize:vertical;">${escHtml(isEdit ? note.content : '')}</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -205,6 +264,22 @@ function openNoteModal({ mode, note = null }) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Formatierungs-Toolbar
|
||||||
|
const textarea = panel.querySelector('#note-content');
|
||||||
|
panel.querySelectorAll('.note-format-btn[data-format]').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
applyFormat(textarea, btn.dataset.format);
|
||||||
|
textarea.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
textarea.addEventListener('keydown', (e) => {
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
if (e.key === 'b') { e.preventDefault(); applyFormat(textarea, 'bold'); }
|
||||||
|
if (e.key === 'i') { e.preventDefault(); applyFormat(textarea, 'italic'); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
panel.querySelector('#note-modal-cancel').addEventListener('click', closeModal);
|
panel.querySelector('#note-modal-cancel').addEventListener('click', closeModal);
|
||||||
|
|
||||||
panel.querySelector('#note-modal-save').addEventListener('click', async () => {
|
panel.querySelector('#note-modal-save').addEventListener('click', async () => {
|
||||||
|
|||||||
+59
-6
@@ -101,7 +101,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity var(--transition-fast), background-color var(--transition-fast);
|
transition: opacity var(--transition-fast), background-color var(--transition-fast);
|
||||||
color: var(--color-text-primary);
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-card__pin::before {
|
.note-card__pin::before {
|
||||||
@@ -127,14 +127,15 @@
|
|||||||
font-size: var(--text-sm);
|
font-size: var(--text-sm);
|
||||||
font-weight: var(--font-weight-semibold);
|
font-weight: var(--font-weight-semibold);
|
||||||
margin-bottom: var(--space-1);
|
margin-bottom: var(--space-1);
|
||||||
color: var(--color-text-primary);
|
color: inherit;
|
||||||
padding-right: var(--space-6);
|
padding-right: var(--space-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-card__content {
|
.note-card__content {
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-sm);
|
||||||
line-height: var(--line-height-relaxed);
|
line-height: var(--line-height-relaxed);
|
||||||
color: var(--color-text-secondary);
|
color: inherit;
|
||||||
|
opacity: 0.78;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,8 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: var(--space-2);
|
margin-top: var(--space-2);
|
||||||
padding-top: var(--space-2);
|
padding-top: var(--space-2);
|
||||||
border-top: 1px solid var(--color-border-subtle);
|
border-top: 1px solid currentColor;
|
||||||
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-card__creator {
|
.note-card__creator {
|
||||||
@@ -157,7 +159,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-1);
|
gap: var(--space-1);
|
||||||
font-size: var(--text-xs);
|
font-size: var(--text-xs);
|
||||||
color: var(--color-text-tertiary);
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-card__avatar {
|
.note-card__avatar {
|
||||||
@@ -184,7 +186,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--color-text-tertiary);
|
color: inherit;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity var(--transition-fast), background-color var(--transition-fast);
|
transition: opacity var(--transition-fast), background-color var(--transition-fast);
|
||||||
}
|
}
|
||||||
@@ -256,3 +258,54 @@
|
|||||||
.note-color-swatch:hover { transform: scale(1.15); }
|
.note-color-swatch:hover { transform: scale(1.15); }
|
||||||
.note-color-swatch--active { border-color: var(--color-text-primary); transform: scale(1.1); }
|
.note-color-swatch--active { border-color: var(--color-text-primary); transform: scale(1.1); }
|
||||||
|
|
||||||
|
/* --------------------------------------------------------
|
||||||
|
* Formatierungs-Toolbar im Notiz-Editor
|
||||||
|
* -------------------------------------------------------- */
|
||||||
|
.note-format-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-1);
|
||||||
|
padding: var(--space-1) var(--space-2);
|
||||||
|
border: 1.5px solid var(--color-border);
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||||
|
background-color: var(--color-surface-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-format-toolbar + .form-input {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-format-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-xs);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color var(--transition-fast), color var(--transition-fast);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-format-btn:hover {
|
||||||
|
background-color: var(--color-accent-light);
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-format-btn--sep {
|
||||||
|
width: 1px;
|
||||||
|
background-color: var(--color-border);
|
||||||
|
margin: var(--space-1) var(--space-1);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-format-btn--sep:hover {
|
||||||
|
background-color: var(--color-border);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -12,9 +12,9 @@
|
|||||||
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
|
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const SHELL_CACHE = 'oikos-shell-v17';
|
const SHELL_CACHE = 'oikos-shell-v18';
|
||||||
const PAGES_CACHE = 'oikos-pages-v17';
|
const PAGES_CACHE = 'oikos-pages-v18';
|
||||||
const ASSETS_CACHE = 'oikos-assets-v17';
|
const ASSETS_CACHE = 'oikos-assets-v18';
|
||||||
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
|
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
|
||||||
|
|
||||||
// App-Shell: sofort benötigt für ersten Render
|
// App-Shell: sofort benötigt für ersten Render
|
||||||
|
|||||||
Reference in New Issue
Block a user