New /prompts section extends the existing database (new Prompt model,
same SQLite file) and mirrors the Links experience: add a prompt, get
it auto-categorized and tagged by AI, filter by category, full-text
search, sort, edit, or have the AI re-categorize it. A header nav
switch ("Links" / "Prompts") toggles between the two collections; the
shared topbar/menu markup was factored into _topbar.html and
_topbar_actions.html so both pages (and settings) stay in sync.
Each prompt card has a dedicated copy-to-clipboard icon next to
edit/delete, so a stored prompt can be reused immediately. Copying
uses the raw markdown source (not the rendered HTML) so structure
survives when pasted into another AI tool. The copy handler tries the
async Clipboard API first and falls back to a hidden-textarea +
execCommand('copy') for plain-http/non-secure contexts, with clear
success/failure icon feedback either way.
Prompt content supports Markdown and is rendered server-side
(app/mdrender.py) for the card preview. Since this is user-supplied
HTML-adjacent content, rendering goes through two defenses: the raw
text is escaped (only '<' and '&', not '>', so blockquotes keep
working) before conversion so no raw tag can survive, and the
resulting HTML is passed through bleach with a tag/attribute/protocol
allowlist so Markdown-generated links can't carry a javascript: URL.
Verified against raw <script>, <img onerror>, and javascript: link
payloads. The edit form always shows the raw Markdown source, never
the rendered HTML.
Also bumps the default APP_VERSION (shown in the footer) from 1.0.0
to 2.0.0 to mark this feature addition.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
92 lines
4.5 KiB
HTML
92 lines
4.5 KiB
HTML
{#
|
||
Gemeinsame Icons (angelehnt an Lucide, MIT-lizenziert), inline als SVG.
|
||
Monochrom über currentColor – Farbe wird vom umgebenden Element gesetzt.
|
||
Verwendung: {% import "_icons.html" as icons %} ... {{ icons.edit() }}
|
||
#}
|
||
|
||
{% macro base(path, size=16) -%}
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="{{ size }}" height="{{ size }}" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||
class="icon" aria-hidden="true">{{ path | safe }}</svg>
|
||
{%- endmacro %}
|
||
|
||
{% macro grid(size=16) -%}
|
||
{{ base('<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro bookmark(filled=False, size=16) -%}
|
||
{{ base('<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"' + (' fill="currentColor"' if filled else '') + '/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro edit(size=16) -%}
|
||
{{ base('<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro trash(size=16) -%}
|
||
{{ base('<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro search(size=16) -%}
|
||
{{ base('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro plus(size=16) -%}
|
||
{{ base('<path d="M5 12h14"/><path d="M12 5v14"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro alert(size=16) -%}
|
||
{{ base('<path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro sparkles(size=16) -%}
|
||
{{ base('<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro clock(size=16) -%}
|
||
{{ base('<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro spinner(size=16) -%}
|
||
{{ base('<path d="M21 12a9 9 0 1 1-6.219-8.56"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro sun(size=16) -%}
|
||
{{ base('<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro moon(size=16) -%}
|
||
{{ base('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro menu(size=16) -%}
|
||
{{ base('<line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="18" y2="18"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro user(size=16) -%}
|
||
{{ base('<circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro log_out(size=16) -%}
|
||
{{ base('<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro sliders(size=16) -%}
|
||
{{ base('<line x1="21" x2="14" y1="4" y2="4"/><line x1="10" x2="3" y1="4" y2="4"/><line x1="21" x2="12" y1="12" y2="12"/><line x1="8" x2="3" y1="12" y2="12"/><line x1="21" x2="16" y1="20" y2="20"/><line x1="12" x2="3" y1="20" y2="20"/><line x1="14" x2="14" y1="2" y2="6"/><line x1="8" x2="8" y1="10" y2="14"/><line x1="16" x2="16" y1="18" y2="22"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro download(size=16) -%}
|
||
{{ base('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro upload(size=16) -%}
|
||
{{ base('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro copy(size=16) -%}
|
||
{{ base('<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>', size) }}
|
||
{%- endmacro %}
|
||
|
||
{% macro tag(size=16) -%}
|
||
{{ base('<path d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"/><circle cx="7.5" cy="7.5" r="1.5"/>', size) }}
|
||
{%- endmacro %}
|