Merge pull request #219 from gamosoft/features/fix-bold-drift

Features/fix bold drift
This commit is contained in:
Guillermo Villar 2026-05-07 11:21:15 +02:00 committed by GitHub
commit 536a9aa80b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 146 additions and 29 deletions

View File

@ -964,31 +964,64 @@ def get_template_content(notes_dir: str, template_name: str) -> Optional[str]:
return None return None
# Matches custom strftime placeholders like {{date:%Y%m%d}} or {{time:%H%M%S}}.
# The three prefixes mirror the bare {{date}}, {{time}}, {{datetime}} placeholders;
# disallowing '{' and '}' inside the format keeps parsing unambiguous and
# guarantees we never swallow an adjacent placeholder.
_STRFTIME_PLACEHOLDER_RE = re.compile(r'\{\{(date|time|datetime):([^{}]+)\}\}')
def apply_template_placeholders(content: str, note_path: str) -> str: def apply_template_placeholders(content: str, note_path: str) -> str:
""" """
Replace template placeholders with actual values. Replace template placeholders with actual values.
Supported placeholders: Built-in named placeholders (fixed default formats):
{{date}} - Current date (YYYY-MM-DD) {{date}} - Current date (YYYY-MM-DD)
{{time}} - Current time (HH:MM:SS) {{time}} - Current time (HH:MM:SS)
{{datetime}} - Current datetime (YYYY-MM-DD HH:MM:SS) {{datetime}} - Current datetime (YYYY-MM-DD HH:MM:SS)
{{timestamp}} - Unix timestamp {{timestamp}} - Unix timestamp (seconds)
{{year}} - Current year (YYYY) {{year}} - Current year (YYYY)
{{month}} - Current month (MM) {{month}} - Current month (MM)
{{day}} - Current day (DD) {{day}} - Current day (DD)
{{title}} - Note name without extension {{title}} - Note name without extension
{{folder}} - Parent folder name {{folder}} - Parent folder name
Custom date/time formats (strftime escape hatch):
{{date:FMT}}, {{time:FMT}}, {{datetime:FMT}}
Any of the three date/time placeholders above also accepts an
optional ":FMT" suffix where FMT is a Python strftime() format
string. Pick the prefix whose default format covers the same
components you want to format. Examples:
{{datetime:%Y%m%d%H%M%S}} -> 20260506154200 (filename-safe stamp)
{{date:%d/%m/%Y}} -> 06/05/2026 (European date)
{{date:%A}} -> Wednesday (full weekday name)
{{time:%H%M%S}} -> 154200 (compact time)
{{date:%V}} -> 19 (ISO week number)
Invalid format strings are left in the output unchanged so a typo
is visible rather than silently swallowed.
Args: Args:
content: Template content with placeholders content: Template content with placeholders
note_path: Path of the note being created note_path: Path of the note being created
Returns: Returns:
Content with placeholders replaced Content with placeholders replaced
""" """
now = datetime.now() now = datetime.now()
note = Path(note_path) note = Path(note_path)
def _strftime_sub(match: 're.Match[str]') -> str:
fmt = match.group(2)
try:
return now.strftime(fmt)
except (ValueError, TypeError):
return match.group(0)
content = _STRFTIME_PLACEHOLDER_RE.sub(_strftime_sub, content)
replacements = { replacements = {
'{{date}}': now.strftime('%Y-%m-%d'), '{{date}}': now.strftime('%Y-%m-%d'),
'{{time}}': now.strftime('%H:%M:%S'), '{{time}}': now.strftime('%H:%M:%S'),
@ -1000,11 +1033,11 @@ def apply_template_placeholders(content: str, note_path: str) -> str:
'{{title}}': note.stem, '{{title}}': note.stem,
'{{folder}}': note.parent.name if str(note.parent) != '.' else 'Root', '{{folder}}': note.parent.name if str(note.parent) != '.' else 'Root',
} }
result = content result = content
for placeholder, value in replacements.items(): for placeholder, value in replacements.items():
result = result.replace(placeholder, value) result = result.replace(placeholder, value)
return result return result

View File

@ -287,6 +287,7 @@ Create notes from reusable templates with dynamic placeholder replacement.
- `{{day}}` - Current day (DD) - `{{day}}` - Current day (DD)
- `{{title}}` - Note name (without extension) - `{{title}}` - Note name (without extension)
- `{{folder}}` - Parent folder name - `{{folder}}` - Parent folder name
- `{{date:FORMAT}}` / `{{time:FORMAT}}` / `{{datetime:FORMAT}}` - Custom format using any Python `strftime()` string, e.g. `{{datetime:%Y%m%d%H%M%S}}``20251126143045`. See [Templates](TEMPLATES.md#custom-datetime-formats) for the full recipe list.
### Example Template ### Example Template
```markdown ```markdown

View File

@ -46,6 +46,39 @@ Templates support dynamic placeholders that are replaced when you create a note:
| `{{title}}` | Note name (without .md) | `Weekly Meeting` | | `{{title}}` | Note name (without .md) | `Weekly Meeting` |
| `{{folder}}` | Parent folder name | `Projects` | | `{{folder}}` | Parent folder name | `Projects` |
### Custom date/time formats
Need a different format than the defaults above? Any of the three
date/time placeholders — `{{date}}`, `{{time}}`, `{{datetime}}` — accepts
an optional `:FORMAT` suffix to override its default format. `FORMAT` is
any [Python `strftime()` format string](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes),
so anything `strftime` can do, your template can do.
> **One rule to remember:** pick the prefix whose default already covers
> the components you want to format. Use `{{date:…}}` for date-only
> formats, `{{time:…}}` for time-only formats, and `{{datetime:…}}` when
> you need both.
#### Common recipes
| Template | Result | Use case |
|---|---|---|
| `{{date:%Y%m%d}}` | `20251126` | Date stamp for filenames |
| `{{time:%H%M%S}}` | `143045` | Time stamp for filenames |
| `{{datetime:%Y%m%d%H%M%S}}` | `20251126143045` | Sortable, filename-safe full stamp |
| `{{datetime:%Y%m%dT%H%M%S}}` | `20251126T143045` | ISO-8601 basic, no separators |
| `{{datetime:%Y-%m-%dT%H:%M:%S}}` | `2025-11-26T14:30:45` | ISO-8601 extended |
| `{{date:%d/%m/%Y}}` | `26/11/2025` | European-style date |
| `{{date:%m/%d/%Y}}` | `11/26/2025` | US-style date |
| `{{date:%A}}` | `Wednesday` | Full weekday name |
| `{{date:%a}}` | `Wed` | Short weekday name |
| `{{date:%B}}` | `November` | Full month name |
| `{{date:%V}}` | `48` | ISO week number |
| `{{date:%j}}` | `330` | Day of year (1366) |
> Invalid format strings (typos) are left in the output unchanged so the
> mistake is visible rather than silently swallowed.
### Example Template ### Example Template
```markdown ```markdown

View File

@ -703,17 +703,17 @@
to { opacity: 1; } to { opacity: 1; }
} }
/* Syntax highlight colors using theme variables */ /* Syntax highlight colors (color-only — see issue #212) */
.syntax-overlay .md-heading { color: var(--accent-primary); font-weight: 600; } .syntax-overlay .md-heading { color: var(--accent-primary); }
.syntax-overlay .md-bold { color: var(--text-primary); font-weight: 700; } .syntax-overlay .md-bold { color: var(--syntax-bold, #fbbf24); }
.syntax-overlay .md-italic { color: var(--text-secondary); font-style: italic; } .syntax-overlay .md-italic { color: var(--syntax-italic, #a78bfa); }
.syntax-overlay .md-code { color: var(--accent-secondary, var(--accent-primary)); background: var(--bg-tertiary); border-radius: 2px; } .syntax-overlay .md-code { color: var(--accent-secondary, var(--accent-primary)); background: var(--bg-tertiary); border-radius: 2px; }
.syntax-overlay .md-link { color: var(--link-color, var(--accent-primary)); } .syntax-overlay .md-link { color: var(--link-color, var(--accent-primary)); }
.syntax-overlay .md-link-url { color: var(--text-tertiary); } .syntax-overlay .md-link-url { color: var(--text-tertiary); }
.syntax-overlay .md-list { color: var(--accent-primary); } .syntax-overlay .md-list { color: var(--accent-primary); }
.syntax-overlay .md-blockquote { color: var(--text-tertiary); font-style: italic; } .syntax-overlay .md-blockquote{ color: var(--text-tertiary); }
.syntax-overlay .md-hr { color: var(--border-primary); } .syntax-overlay .md-hr { color: var(--border-primary); }
.syntax-overlay .md-wikilink { color: var(--link-internal, var(--accent-primary)); } .syntax-overlay .md-wikilink { color: var(--link-internal, var(--accent-primary)); }
.syntax-overlay .md-frontmatter { color: var(--text-tertiary); } .syntax-overlay .md-frontmatter { color: var(--text-tertiary); }
.syntax-overlay .md-codeblock { color: var(--text-secondary); background: var(--bg-tertiary); } .syntax-overlay .md-codeblock { color: var(--text-secondary); background: var(--bg-tertiary); }

View File

@ -28,7 +28,13 @@
--success: #3ad900; --success: #3ad900;
--error: #ff0088; --error: #ff0088;
--warning: #ffc600; --warning: #ffc600;
/* Bold + italic Cobalt2 native orange + cyan.
Heading is yellow (warm), so italic uses Cobalt2's signature cyan
instead of "lighter primary" to preserve warm/cool hierarchy. */
--syntax-bold: #ff9d00; /* cobalt2 orange */
--syntax-italic: #9effff; /* cobalt2 cyan */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.4); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5);

View File

@ -26,7 +26,11 @@
--success: #34d399; --success: #34d399;
--error: #f87171; --error: #f87171;
--warning: #fbbf24; --warning: #fbbf24;
/* Bold + italic — warm emphasis + lighter primary for italic (cohesive with heading) */
--syntax-bold: #fbbf24; /* amber/yellow */
--syntax-italic: #93c5fd; /* lighter blue — same family as heading, distinct by luminance */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);

View File

@ -28,7 +28,11 @@
--success: #50fa7b; --success: #50fa7b;
--error: #ff5555; --error: #ff5555;
--warning: #f1fa8c; --warning: #f1fa8c;
/* Bold + italic — Dracula yellow + lighter purple (cohesive with purple heading) */
--syntax-bold: #f1fa8c; /* Dracula yellow */
--syntax-italic: #d8b4fe; /* lighter purple — same family as heading */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);

View File

@ -28,7 +28,11 @@
--success: #b8bb26; /* Green */ --success: #b8bb26; /* Green */
--error: #fb4934; /* Red */ --error: #fb4934; /* Red */
--warning: #fabd2f; /* Yellow */ --warning: #fabd2f; /* Yellow */
/* Bold + italic — Gruvbox yellow + lighter peach (cohesive with orange heading) */
--syntax-bold: #fabd2f; /* gruvbox yellow */
--syntax-italic: #fdba74; /* lighter peach — same family as orange heading */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.4); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5);

View File

@ -28,6 +28,10 @@
--error: #ff6b8a; --error: #ff6b8a;
--warning: #ffd447; --warning: #ffd447;
/* Bold + italic — amber CRT alert + lighter matrix green (same green family) */
--syntax-bold: #ffd447; /* amber CRT alert (historically authentic — amber CRTs existed) */
--syntax-italic: #86efac; /* lighter green — same family as matrix-green heading */
/* Shadows — heavy, like a dim room */ /* Shadows — heavy, like a dim room */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.55); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.55);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.65); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.65);

View File

@ -26,7 +26,11 @@
--success: #10b981; --success: #10b981;
--error: #ef4444; --error: #ef4444;
--warning: #f59e0b; --warning: #f59e0b;
/* Bold + italic — warm emphasis + deeper primary for italic (cohesive on white) */
--syntax-bold: #b45309; /* deep amber */
--syntax-italic: #1d4ed8; /* deeper blue — same family as heading, distinct by depth */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);

View File

@ -28,7 +28,11 @@
--success: #5da60a; --success: #5da60a;
--error: #d9534f; --error: #d9534f;
--warning: #f0ad4e; --warning: #f0ad4e;
/* Bold + italic — deep amber + deeper matcha green (cohesive with green heading on lime) */
--syntax-bold: #a16207; /* deep amber — warm contrast on lime */
--syntax-italic: #365314; /* deeper green — same family as matcha heading */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(58, 77, 34, 0.1); --shadow-sm: 0 1px 2px 0 rgba(58, 77, 34, 0.1);
--shadow-md: 0 4px 6px -1px rgba(58, 77, 34, 0.15); --shadow-md: 0 4px 6px -1px rgba(58, 77, 34, 0.15);

View File

@ -28,7 +28,11 @@
--success: #a6e22e; --success: #a6e22e;
--error: #f92672; --error: #f92672;
--warning: #e6db74; --warning: #e6db74;
/* Bold + italic — Monokai yellow + lighter cyan (cohesive with cyan heading) */
--syntax-bold: #e6db74; /* Monokai yellow (strings) */
--syntax-italic: #a5f3fc; /* lighter cyan — same family as heading */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);

View File

@ -28,7 +28,11 @@
--success: #a3be8c; --success: #a3be8c;
--error: #bf616a; --error: #bf616a;
--warning: #ebcb8b; --warning: #ebcb8b;
/* Bold + italic — aurora yellow + lighter frost (cohesive with frost-cyan heading) */
--syntax-bold: #ebcb8b; /* nord13 — aurora yellow */
--syntax-italic: #bae6fd; /* lighter frost — same family as heading, distinct by luminance */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);

View File

@ -28,6 +28,10 @@
--error: #f07171; --error: #f07171;
--warning: #ef7c2a; --warning: #ef7c2a;
/* Bold + italic — Ubuntu gold + lighter peach (cohesive with orange heading) */
--syntax-bold: #ffce6b; /* warm gold */
--syntax-italic: #fdba74; /* lighter peach — same family as orange heading */
/* Shadows — widget.shadow */ /* Shadows — widget.shadow */
--shadow-sm: 0 1px 2px 0 rgba(6, 1, 4, 0.55); --shadow-sm: 0 1px 2px 0 rgba(6, 1, 4, 0.55);
--shadow-md: 0 4px 6px -1px rgba(6, 1, 4, 0.6); --shadow-md: 0 4px 6px -1px rgba(6, 1, 4, 0.6);

View File

@ -28,7 +28,11 @@
--success: #388a34; --success: #388a34;
--error: #e51400; --error: #e51400;
--warning: #f6a623; --warning: #f6a623;
/* Bold + italic — VS brown-amber + deeper VS blue (cohesive with blue heading on white) */
--syntax-bold: #795e26; /* VS function brown-amber */
--syntax-italic: #1e40af; /* deeper blue — same family as heading, distinct by depth */
/* Shadows */ /* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.08); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.08);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.12); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.12);

View File

@ -28,7 +28,11 @@
--success: #4caf50; --success: #4caf50;
--error: #ff5252; --error: #ff5252;
--warning: #ffb74d; --warning: #ffb74d;
/* Bold + italic — warm amber + lighter Vue green (cohesive with green heading) */
--syntax-bold: #ffb74d; /* warm amber */
--syntax-italic: #86efac; /* lighter green — same family as heading, distinct by luminance */
/* Shadows - deeper for high contrast */ /* Shadows - deeper for high contrast */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.6); --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.6);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.7); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.7);