botui/ui/suite/paper/paper.html

2415 lines
81 KiB
HTML
Raw Normal View History

2025-12-03 18:42:22 -03:00
<!-- Paper - AI Writing & Notes -->
<div class="paper-container" id="paper-app">
<!-- Sidebar - Notes List -->
<aside class="paper-sidebar" id="paper-sidebar">
<div class="sidebar-header">
<h2 data-i18n="paper-title">Paper</h2>
<button
class="btn-icon"
title="New Note"
hx-post="/api/ui/paper/new"
hx-target="#paper-list"
hx-swap="afterbegin"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
<!-- Search Notes -->
<div class="sidebar-search">
<input
type="text"
data-i18n-placeholder="paper-search-notes"
placeholder="Search notes..."
name="q"
hx-get="/api/ui/paper/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#paper-list"
/>
<svg
class="search-icon"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</div>
<!-- Notes List -->
<div
class="paper-list"
id="paper-list"
hx-get="/api/ui/paper/list"
hx-trigger="load"
hx-swap="innerHTML"
>
2025-12-03 18:42:22 -03:00
<!-- Notes loaded here -->
</div>
<!-- AI Templates -->
<div class="sidebar-section">
<h3 data-i18n="paper-quick-start">Quick Start</h3>
2025-12-03 18:42:22 -03:00
<div class="template-grid">
<button
class="template-btn"
hx-post="/api/ui/paper/template/blank"
hx-target="#editor-content"
>
2025-12-03 18:42:22 -03:00
<span class="template-icon">📄</span>
<span data-i18n="paper-template-blank">Blank</span>
2025-12-03 18:42:22 -03:00
</button>
<button
class="template-btn"
hx-post="/api/ui/paper/template/meeting"
hx-target="#editor-content"
>
2025-12-03 18:42:22 -03:00
<span class="template-icon">📋</span>
<span data-i18n="paper-template-meeting">Meeting</span>
2025-12-03 18:42:22 -03:00
</button>
<button
class="template-btn"
hx-post="/api/ui/paper/template/todo"
hx-target="#editor-content"
>
2025-12-03 18:42:22 -03:00
<span class="template-icon"></span>
<span data-i18n="paper-template-todo">To-Do</span>
2025-12-03 18:42:22 -03:00
</button>
<button
class="template-btn"
hx-post="/api/ui/paper/template/research"
hx-target="#editor-content"
>
2025-12-03 18:42:22 -03:00
<span class="template-icon">🔬</span>
<span data-i18n="paper-template-research">Research</span>
2025-12-03 18:42:22 -03:00
</button>
</div>
</div>
</aside>
<!-- Main Editor Area -->
<main class="paper-main">
<!-- Editor Toolbar -->
<div class="editor-toolbar" id="editor-toolbar">
<!-- Left: Document Actions -->
<div class="toolbar-left">
<button
class="btn-icon"
id="toggle-sidebar"
title="Toggle Sidebar"
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
></rect>
2025-12-03 18:42:22 -03:00
<line x1="9" y1="3" x2="9" y2="21"></line>
</svg>
</button>
<span class="toolbar-divider"></span>
<button class="btn-icon" data-cmd="undo" title="Undo (Ctrl+Z)">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<path d="M3 7v6h6"></path>
<path
d="M21 17a9 9 0 00-9-9 9 9 0 00-6 2.3L3 13"
></path>
2025-12-03 18:42:22 -03:00
</svg>
</button>
<button class="btn-icon" data-cmd="redo" title="Redo (Ctrl+Y)">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<path d="M21 7v6h-6"></path>
<path d="M3 17a9 9 0 019-9 9 9 0 016 2.3l3 2.7"></path>
</svg>
</button>
</div>
<!-- Center: Formatting -->
<div class="toolbar-center">
<div class="toolbar-group">
<select
class="toolbar-select"
id="heading-select"
title="Heading Style"
>
2025-12-03 18:42:22 -03:00
<option value="p">Paragraph</option>
<option value="h1">Heading 1</option>
<option value="h2">Heading 2</option>
<option value="h3">Heading 3</option>
</select>
</div>
<span class="toolbar-divider"></span>
<div class="toolbar-group">
<button
class="btn-icon"
data-cmd="bold"
title="Bold (Ctrl+B)"
>
2025-12-03 18:42:22 -03:00
<strong>B</strong>
</button>
<button
class="btn-icon"
data-cmd="italic"
title="Italic (Ctrl+I)"
>
2025-12-03 18:42:22 -03:00
<em>I</em>
</button>
<button
class="btn-icon"
data-cmd="underline"
title="Underline (Ctrl+U)"
>
2025-12-03 18:42:22 -03:00
<u>U</u>
</button>
<button
class="btn-icon"
data-cmd="strikethrough"
title="Strikethrough"
>
2025-12-03 18:42:22 -03:00
<s>S</s>
</button>
</div>
<span class="toolbar-divider"></span>
<div class="toolbar-group">
<button
class="btn-icon"
data-cmd="highlight"
title="Highlight"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M15.243 4.515l-6.738 6.737-.707 2.121-1.04 1.041 2.828 2.829 1.04-1.041 2.122-.707 6.737-6.738-4.242-4.242zm6.364 3.535a1 1 0 01-1.414 0l-4.243-4.243a1 1 0 010-1.414l.707-.707a1 1 0 011.414 0l4.243 4.243a1 1 0 010 1.414l-.707.707zM4.283 16.89l2.828 2.829-1.414 1.414-4.243-1.414 2.829-2.829z"
></path>
2025-12-03 18:42:22 -03:00
</svg>
</button>
<input
type="color"
class="color-picker"
id="text-color"
value="#000000"
title="Text Color"
/>
2025-12-03 18:42:22 -03:00
</div>
<span class="toolbar-divider"></span>
<div class="toolbar-group">
<button
class="btn-icon"
data-cmd="alignLeft"
title="Align Left"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="17" y1="10" x2="3" y2="10"></line>
<line x1="21" y1="6" x2="3" y2="6"></line>
<line x1="21" y1="14" x2="3" y2="14"></line>
<line x1="17" y1="18" x2="3" y2="18"></line>
</svg>
</button>
<button
class="btn-icon"
data-cmd="alignCenter"
title="Align Center"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="18" y1="10" x2="6" y2="10"></line>
<line x1="21" y1="6" x2="3" y2="6"></line>
<line x1="21" y1="14" x2="3" y2="14"></line>
<line x1="18" y1="18" x2="6" y2="18"></line>
</svg>
</button>
<button
class="btn-icon"
data-cmd="alignRight"
title="Align Right"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="21" y1="10" x2="7" y2="10"></line>
<line x1="21" y1="6" x2="3" y2="6"></line>
<line x1="21" y1="14" x2="3" y2="14"></line>
<line x1="21" y1="18" x2="7" y2="18"></line>
</svg>
</button>
</div>
<span class="toolbar-divider"></span>
<div class="toolbar-group">
<button
class="btn-icon"
data-cmd="bulletList"
title="Bullet List"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="8" y1="6" x2="21" y2="6"></line>
<line x1="8" y1="12" x2="21" y2="12"></line>
<line x1="8" y1="18" x2="21" y2="18"></line>
<circle
cx="4"
cy="6"
r="1"
fill="currentColor"
></circle>
<circle
cx="4"
cy="12"
r="1"
fill="currentColor"
></circle>
<circle
cx="4"
cy="18"
r="1"
fill="currentColor"
></circle>
2025-12-03 18:42:22 -03:00
</svg>
</button>
<button
class="btn-icon"
data-cmd="numberedList"
title="Numbered List"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="10" y1="6" x2="21" y2="6"></line>
<line x1="10" y1="12" x2="21" y2="12"></line>
<line x1="10" y1="18" x2="21" y2="18"></line>
<text x="3" y="8" font-size="8" fill="currentColor">
1
</text>
<text
x="3"
y="14"
font-size="8"
fill="currentColor"
>
2
</text>
<text
x="3"
y="20"
font-size="8"
fill="currentColor"
>
3
</text>
2025-12-03 18:42:22 -03:00
</svg>
</button>
<button
class="btn-icon"
data-cmd="todoList"
title="To-Do List"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="5"
width="6"
height="6"
rx="1"
></rect>
2025-12-03 18:42:22 -03:00
<line x1="12" y1="8" x2="21" y2="8"></line>
<path d="M4 14l2 2 4-4"></path>
<line x1="12" y1="16" x2="21" y2="16"></line>
</svg>
</button>
</div>
<span class="toolbar-divider"></span>
<div class="toolbar-group">
<button
class="btn-icon"
data-cmd="link"
title="Insert Link"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"
></path>
<path
d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"
></path>
2025-12-03 18:42:22 -03:00
</svg>
</button>
<button
class="btn-icon"
data-cmd="image"
title="Insert Image"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
></rect>
2025-12-03 18:42:22 -03:00
<circle cx="8.5" cy="8.5" r="1.5"></circle>
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
</button>
<button
class="btn-icon"
data-cmd="table"
title="Insert Table"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
></rect>
2025-12-03 18:42:22 -03:00
<line x1="3" y1="9" x2="21" y2="9"></line>
<line x1="3" y1="15" x2="21" y2="15"></line>
<line x1="9" y1="3" x2="9" y2="21"></line>
<line x1="15" y1="3" x2="15" y2="21"></line>
</svg>
</button>
<button class="btn-icon" data-cmd="code" title="Code Block">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<polyline points="16 18 22 12 16 6"></polyline>
<polyline points="8 6 2 12 8 18"></polyline>
</svg>
</button>
<button class="btn-icon" data-cmd="quote" title="Quote">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"
></path>
2025-12-03 18:42:22 -03:00
</svg>
</button>
</div>
</div>
<!-- Right: AI Actions -->
<div class="toolbar-right">
<button class="btn-ai" id="ai-assist-btn" title="AI Assistant">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 2a2 2 0 012 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 017 7h1a1 1 0 011 1v3a1 1 0 01-1 1h-1v1a2 2 0 01-2 2H5a2 2 0 01-2-2v-1H2a1 1 0 01-1-1v-3a1 1 0 011-1h1a7 7 0 017-7h1V5.73c-.6-.34-1-.99-1-1.73a2 2 0 012-2z"
></path>
2025-12-03 18:42:22 -03:00
<circle cx="9" cy="13" r="1"></circle>
<circle cx="15" cy="13" r="1"></circle>
<path d="M9 17h6"></path>
</svg>
<span>AI</span>
</button>
<button
class="btn-icon"
hx-post="/api/ui/paper/save"
hx-include="#editor-content"
title="Save"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"
></path>
2025-12-03 18:42:22 -03:00
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
</button>
<button class="btn-icon" id="export-btn" title="Export">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"
></path>
2025-12-03 18:42:22 -03:00
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
</button>
</div>
</div>
<!-- Document Canvas -->
<div class="editor-canvas">
<div class="paper-page">
<!-- Title -->
<div
class="paper-title"
contenteditable="true"
data-placeholder="Untitled"
data-i18n-placeholder="paper-untitled"
id="paper-title"
></div>
2025-12-03 18:42:22 -03:00
<!-- Editor Content -->
<div
class="editor-content"
id="editor-content"
contenteditable="true"
data-placeholder="Start writing, or type / for commands..."
data-i18n-placeholder="paper-placeholder"
hx-post="/api/ui/paper/autosave"
hx-trigger="keyup changed delay:2000ms"
hx-swap="none"
></div>
2025-12-03 18:42:22 -03:00
</div>
</div>
<!-- Slash Command Menu -->
<div class="slash-menu hidden" id="slash-menu">
<div class="slash-menu-header" data-i18n="paper-commands">
Commands
</div>
2025-12-03 18:42:22 -03:00
<div class="slash-menu-items">
<button class="slash-item" data-cmd="h1">
<span class="slash-icon">H1</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-heading1"
>Heading 1</span
>
<span class="slash-desc" data-i18n="paper-heading1-desc"
>Large section heading</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="h2">
<span class="slash-icon">H2</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-heading2"
>Heading 2</span
>
<span class="slash-desc" data-i18n="paper-heading2-desc"
>Medium section heading</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="h3">
<span class="slash-icon">H3</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-heading3"
>Heading 3</span
>
<span class="slash-desc" data-i18n="paper-heading3-desc"
>Small section heading</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="bullet">
<span class="slash-icon"></span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-bullet-list"
>Bullet List</span
>
<span
class="slash-desc"
data-i18n="paper-bullet-list-desc"
>Create a bullet list</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="number">
<span class="slash-icon">1.</span>
<div class="slash-text">
<span
class="slash-label"
data-i18n="paper-numbered-list"
>Numbered List</span
>
<span
class="slash-desc"
data-i18n="paper-numbered-list-desc"
>Create a numbered list</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="todo">
<span class="slash-icon"></span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-todo-list"
>To-Do</span
>
<span
class="slash-desc"
data-i18n="paper-todo-list-desc"
>Track tasks with checkboxes</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="quote">
<span class="slash-icon">"</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-quote"
>Quote</span
>
<span class="slash-desc" data-i18n="paper-quote-desc"
>Capture a quote</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="code">
<span class="slash-icon">&lt;/&gt;</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-code-block"
>Code Block</span
>
<span
class="slash-desc"
data-i18n="paper-code-block-desc"
>Display code with syntax</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="divider">
<span class="slash-icon"></span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-divider"
>Divider</span
>
<span class="slash-desc" data-i18n="paper-divider-desc"
>Visual section break</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="callout">
<span class="slash-icon">💡</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-callout"
>Callout</span
>
<span class="slash-desc" data-i18n="paper-callout-desc"
>Highlight important info</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="table">
<span class="slash-icon"></span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-table"
>Table</span
>
<span class="slash-desc" data-i18n="paper-table-desc"
>Add a table</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item" data-cmd="image">
<span class="slash-icon">🖼</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-image"
>Image</span
>
<span class="slash-desc" data-i18n="paper-image-desc"
>Upload or embed image</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<div class="slash-menu-divider"></div>
<div
class="slash-menu-header"
data-i18n="paper-ai-quick-actions"
>
AI Actions
</div>
2025-12-03 18:42:22 -03:00
<button class="slash-item ai-item" data-cmd="ai-write">
<span class="slash-icon"></span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-ai-write"
>AI Write</span
>
<span class="slash-desc" data-i18n="paper-ai-write-desc"
>Generate content from prompt</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item ai-item" data-cmd="ai-summarize">
<span class="slash-icon">📝</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-ai-summarize"
>Summarize</span
>
<span
class="slash-desc"
data-i18n="paper-ai-summarize-desc"
>Condense selected text</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item ai-item" data-cmd="ai-expand">
<span class="slash-icon">📖</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-ai-expand"
>Expand</span
>
<span
class="slash-desc"
data-i18n="paper-ai-expand-desc"
>Add more detail</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item ai-item" data-cmd="ai-improve">
<span class="slash-icon">✏️</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-ai-improve"
>Improve Writing</span
>
<span
class="slash-desc"
data-i18n="paper-ai-improve-desc"
>Fix grammar & clarity</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item ai-item" data-cmd="ai-translate">
<span class="slash-icon">🌐</span>
<div class="slash-text">
<span class="slash-label" data-i18n="paper-ai-translate"
>Translate</span
>
<span
class="slash-desc"
data-i18n="paper-ai-translate-desc"
>Convert to another language</span
>
2025-12-03 18:42:22 -03:00
</div>
</button>
<button class="slash-item ai-item" data-cmd="ai-extract">
<span class="slash-icon">📋</span>
<div class="slash-text">
<span class="slash-label">Extract Actions</span>
<span class="slash-desc">Pull tasks from text</span>
</div>
</button>
</div>
</div>
<!-- AI Assistant Panel -->
<div class="ai-panel hidden" id="ai-panel">
<div class="ai-panel-header">
<h3 data-i18n="paper-ai-assistant">AI Assistant</h3>
2025-12-03 18:42:22 -03:00
<button class="btn-icon" id="close-ai-panel">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="ai-panel-content">
<!-- Quick Actions -->
<div class="ai-quick-actions">
<button
class="ai-action-btn"
hx-post="/api/ui/paper/ai/summarize"
hx-include="[name='selected-text']"
hx-target="#ai-response-content"
hx-swap="innerHTML"
>
<span>📝</span>
<span data-i18n="paper-ai-summarize">Summarize</span>
2025-12-03 18:42:22 -03:00
</button>
<button
class="ai-action-btn"
hx-post="/api/ui/paper/ai/expand"
hx-include="[name='selected-text']"
hx-target="#ai-response-content"
hx-swap="innerHTML"
>
<span>📖</span>
<span data-i18n="paper-ai-expand">Expand</span>
2025-12-03 18:42:22 -03:00
</button>
<button
class="ai-action-btn"
hx-post="/api/ui/paper/ai/improve"
hx-include="[name='selected-text']"
hx-target="#ai-response-content"
hx-swap="innerHTML"
>
<span>✏️</span>
<span data-i18n="paper-ai-improve">Improve</span>
2025-12-03 18:42:22 -03:00
</button>
<button
class="ai-action-btn"
hx-post="/api/ui/paper/ai/simplify"
hx-include="[name='selected-text']"
hx-target="#ai-response-content"
hx-swap="innerHTML"
>
<span>🎯</span>
<span data-i18n="paper-ai-make-shorter">Simplify</span>
2025-12-03 18:42:22 -03:00
</button>
</div>
<!-- Tone Selector -->
<div class="ai-tone-section">
<label data-i18n="paper-ai-tone">Change Tone</label>
2025-12-03 18:42:22 -03:00
<div class="tone-buttons">
<button
class="tone-btn"
data-tone="professional"
data-i18n="paper-ai-tone-professional"
>
Professional
</button>
<button
class="tone-btn"
data-tone="casual"
data-i18n="paper-ai-tone-casual"
>
Casual
</button>
<button
class="tone-btn"
data-tone="friendly"
data-i18n="paper-ai-tone-friendly"
>
Friendly
</button>
<button
class="tone-btn"
data-tone="formal"
data-i18n="paper-ai-tone-formal"
>
Formal
</button>
2025-12-03 18:42:22 -03:00
</div>
</div>
<!-- Translation -->
<div class="ai-translate-section">
<label data-i18n="paper-ai-translate-to"
>Translate to</label
>
2025-12-03 18:42:22 -03:00
<select id="translate-lang" class="ai-select">
<option value="en">English</option>
<option value="pt">Português</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="ja">日本語</option>
2025-12-03 18:42:22 -03:00
</select>
<button
class="ai-action-btn"
hx-post="/api/ui/paper/ai/translate"
hx-include="#translate-lang, [name='selected-text']"
hx-target="#ai-response-content"
hx-swap="innerHTML"
>
<span data-i18n="paper-ai-translate">Translate</span>
2025-12-03 18:42:22 -03:00
</button>
</div>
<!-- Custom Prompt -->
<div class="ai-custom-section">
<label data-i18n="paper-ai-custom-prompt"
>Custom AI Command</label
>
<textarea
id="ai-custom-prompt"
data-i18n-placeholder="paper-ai-custom-placeholder"
placeholder="Tell AI what to do with selected text..."
></textarea>
<button
class="btn-primary"
hx-post="/api/ui/paper/ai/custom"
hx-include="#ai-custom-prompt, [name='selected-text']"
hx-target="#ai-response-content"
hx-swap="innerHTML"
>
<span data-i18n="paper-ai-generate">Run Command</span>
2025-12-03 18:42:22 -03:00
</button>
</div>
<!-- Hidden field for selected text -->
<input
type="hidden"
name="selected-text"
id="selected-text-input"
/>
2025-12-03 18:42:22 -03:00
</div>
<!-- AI Response -->
<div class="ai-response hidden" id="ai-response">
<div class="ai-response-header">
<span data-i18n="paper-ai-response">AI Response</span>
2025-12-03 18:42:22 -03:00
<div class="ai-response-actions">
<button
class="btn-icon"
id="copy-ai-response"
title="Copy"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="9"
y="9"
width="13"
height="13"
rx="2"
ry="2"
></rect>
<path
d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"
></path>
2025-12-03 18:42:22 -03:00
</svg>
</button>
<button
class="btn-icon"
id="insert-ai-response"
title="Insert"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="12" y1="5" x2="12" y2="19"></line>
<polyline points="19 12 12 19 5 12"></polyline>
</svg>
</button>
<button
class="btn-icon"
id="replace-ai-response"
title="Replace Selection"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<path d="M17 1l4 4-4 4"></path>
<path d="M3 11V9a4 4 0 014-4h14"></path>
<path d="M7 23l-4-4 4-4"></path>
<path d="M21 13v2a4 4 0 01-4 4H3"></path>
</svg>
</button>
</div>
</div>
<div class="ai-response-content" id="ai-response-content">
<!-- AI response loads here -->
</div>
</div>
</div>
<!-- Status Bar -->
<div class="paper-status-bar">
<div class="status-left">
<span
class="status-item"
id="word-count"
data-i18n="paper-word-count"
data-i18n-args='{"count": 0}'
>0 words</span
>
<span
class="status-item"
id="char-count"
data-i18n="paper-char-count"
data-i18n-args='{"count": 0}'
>0 characters</span
>
2025-12-03 18:42:22 -03:00
</div>
<div class="status-center">
<span
class="status-item save-status"
id="save-status"
data-i18n="paper-saved"
>Saved</span
>
2025-12-03 18:42:22 -03:00
</div>
<div class="status-right">
<span
class="status-item"
id="last-edited"
data-i18n="paper-last-edited-now"
>Last edited: Just now</span
>
2025-12-03 18:42:22 -03:00
</div>
</div>
</main>
<!-- Export Modal -->
<div class="modal hidden" id="export-modal">
<div class="modal-content">
<div class="modal-header">
<h3 data-i18n="paper-export">Export Document</h3>
2025-12-03 18:42:22 -03:00
<button class="btn-icon close-modal">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
2025-12-03 18:42:22 -03:00
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
<div class="export-options">
<button
class="export-option"
hx-get="/api/ui/paper/export/pdf"
hx-swap="none"
>
2025-12-03 18:42:22 -03:00
<span class="export-icon">📄</span>
<span class="export-label" data-i18n="paper-export-pdf"
>PDF</span
>
2025-12-03 18:42:22 -03:00
</button>
<button
class="export-option"
hx-get="/api/ui/paper/export/docx"
hx-swap="none"
>
2025-12-03 18:42:22 -03:00
<span class="export-icon">📝</span>
<span class="export-label" data-i18n="paper-export-docx"
>Word (.docx)</span
>
2025-12-03 18:42:22 -03:00
</button>
<button
class="export-option"
hx-get="/api/ui/paper/export/md"
hx-swap="none"
>
2025-12-03 18:42:22 -03:00
<span class="export-icon">📑</span>
<span
class="export-label"
data-i18n="paper-export-markdown"
>Markdown</span
>
2025-12-03 18:42:22 -03:00
</button>
<button
class="export-option"
hx-get="/api/ui/paper/export/html"
hx-swap="none"
>
2025-12-03 18:42:22 -03:00
<span class="export-icon">🌐</span>
<span class="export-label">HTML</span>
</button>
<button
class="export-option"
hx-get="/api/ui/paper/export/txt"
hx-swap="none"
>
2025-12-03 18:42:22 -03:00
<span class="export-icon">📋</span>
<span class="export-label">Plain Text</span>
</button>
</div>
</div>
</div>
</div>
</div>
<style>
/* Paper Container */
.paper-container {
display: flex;
height: calc(100vh - 60px);
background: var(--background);
color: var(--foreground);
}
/* Sidebar */
2025-12-03 18:42:22 -03:00
.paper-sidebar {
width: 280px;
border-right: 1px solid var(--border);
background: var(--card);
display: flex;
flex-direction: column;
transition: width 0.2s ease;
}
.paper-sidebar.collapsed {
width: 0;
overflow: hidden;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid var(--border);
}
.sidebar-header h2 {
font-size: 18px;
font-weight: 600;
margin: 0;
}
.sidebar-search {
padding: 12px 16px;
position: relative;
}
.sidebar-search input {
width: 100%;
padding: 8px 12px 8px 36px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--background);
color: var(--foreground);
font-size: 14px;
}
.sidebar-search input:focus {
outline: none;
border-color: var(--primary);
}
.sidebar-search .search-icon {
2025-12-03 18:42:22 -03:00
position: absolute;
left: 28px;
top: 50%;
transform: translateY(-50%);
color: var(--muted-foreground);
2025-12-03 18:42:22 -03:00
}
.paper-list {
flex: 1;
overflow-y: auto;
padding: 8px;
}
.sidebar-section {
padding: 16px;
border-top: 1px solid var(--border);
}
.sidebar-section h3 {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
color: var(--muted-foreground);
margin: 0 0 12px 0;
}
.template-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.template-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 12px 8px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--background);
color: var(--foreground);
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.template-btn:hover {
background: var(--accent);
border-color: var(--primary);
}
.template-icon {
font-size: 20px;
}
/* Main Editor */
.paper-main {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
/* Toolbar */
.editor-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
border-bottom: 1px solid var(--border);
background: var(--card);
gap: 16px;
}
.toolbar-left,
.toolbar-center,
.toolbar-right {
display: flex;
align-items: center;
gap: 4px;
}
.toolbar-group {
display: flex;
align-items: center;
gap: 2px;
}
.toolbar-divider {
width: 1px;
height: 24px;
background: var(--border);
margin: 0 8px;
}
.toolbar-select {
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--background);
color: var(--foreground);
font-size: 13px;
cursor: pointer;
}
.btn-icon {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--foreground);
cursor: pointer;
transition: all 0.15s;
}
.btn-icon:hover {
background: var(--accent);
}
.btn-icon.active {
background: var(--primary);
color: var(--primary-foreground);
}
.btn-ai {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border: none;
border-radius: 6px;
background: linear-gradient(135deg, var(--primary), var(--chart-2));
color: white;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
2025-12-03 18:42:22 -03:00
}
.btn-ai:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.color-picker {
width: 28px;
height: 28px;
padding: 0;
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
}
/* Editor Canvas */
2025-12-03 18:42:22 -03:00
.editor-canvas {
flex: 1;
overflow-y: auto;
padding: 40px;
display: flex;
justify-content: center;
background: var(--muted);
2025-12-03 18:42:22 -03:00
}
.paper-page {
width: 100%;
max-width: 800px;
min-height: calc(100vh - 200px);
padding: 60px 80px;
background: var(--card);
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
2025-12-03 18:42:22 -03:00
}
.paper-title {
font-size: 36px;
font-weight: 700;
margin-bottom: 24px;
outline: none;
border: none;
}
.paper-title:empty:before {
content: attr(data-placeholder);
color: var(--muted-foreground);
}
.editor-content {
font-size: 16px;
line-height: 1.7;
outline: none;
min-height: 400px;
}
.editor-content:empty:before {
content: attr(data-placeholder);
color: var(--muted-foreground);
}
.editor-content h1 {
font-size: 32px;
font-weight: 700;
margin: 24px 0 16px;
}
.editor-content h2 {
font-size: 24px;
font-weight: 600;
margin: 20px 0 12px;
}
.editor-content h3 {
font-size: 20px;
font-weight: 600;
margin: 16px 0 8px;
}
.editor-content p {
margin: 12px 0;
}
.editor-content ul,
.editor-content ol {
padding-left: 24px;
margin: 12px 0;
}
.editor-content li {
margin: 4px 0;
}
.editor-content blockquote {
border-left: 4px solid var(--primary);
padding-left: 16px;
margin: 16px 0;
color: var(--muted-foreground);
font-style: italic;
}
.editor-content code {
background: var(--muted);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
}
.editor-content pre {
background: var(--muted);
padding: 16px;
border-radius: 8px;
overflow-x: auto;
}
.editor-content mark {
background: rgba(255, 255, 0, 0.3);
padding: 2px 4px;
border-radius: 2px;
}
.editor-content a {
color: var(--primary);
text-decoration: underline;
}
.editor-content hr {
border: none;
border-top: 2px solid var(--border);
margin: 24px 0;
}
.editor-content .callout {
background: var(--accent);
border-left: 4px solid var(--primary);
padding: 16px;
border-radius: 4px;
margin: 16px 0;
}
.editor-content .todo-item {
display: flex;
align-items: flex-start;
gap: 8px;
}
.editor-content .todo-checkbox {
margin-top: 4px;
2025-12-03 18:42:22 -03:00
}
/* Slash Menu */
.slash-menu {
position: absolute;
width: 320px;
max-height: 400px;
overflow-y: auto;
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
z-index: 100;
}
.slash-menu.hidden {
2025-12-03 18:42:22 -03:00
display: none;
}
.slash-menu-header {
padding: 8px 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
color: var(--muted-foreground);
background: var(--muted);
}
2025-12-03 18:42:22 -03:00
.slash-menu-items {
padding: 4px;
}
2025-12-03 18:42:22 -03:00
.slash-menu-divider {
height: 1px;
background: var(--border);
margin: 4px 0;
}
2025-12-03 18:42:22 -03:00
.slash-item {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 8px 12px;
border: none;
border-radius: 4px;
background: transparent;
color: var(--foreground);
text-align: left;
cursor: pointer;
transition: background 0.15s;
}
2025-12-03 18:42:22 -03:00
.slash-item:hover {
background: var(--accent);
}
2025-12-03 18:42:22 -03:00
.slash-item.ai-item {
background: linear-gradient(
90deg,
transparent,
rgba(var(--primary-rgb), 0.05)
);
}
2025-12-03 18:42:22 -03:00
.slash-item.ai-item:hover {
background: linear-gradient(
90deg,
var(--accent),
rgba(var(--primary-rgb), 0.1)
);
}
2025-12-03 18:42:22 -03:00
.slash-icon {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
background: var(--muted);
font-size: 14px;
font-weight: 600;
2025-12-03 18:42:22 -03:00
}
.slash-text {
display: flex;
flex-direction: column;
2025-12-03 18:42:22 -03:00
}
.slash-label {
font-size: 14px;
font-weight: 500;
}
2025-12-03 18:42:22 -03:00
.slash-desc {
font-size: 12px;
color: var(--muted-foreground);
}
2025-12-03 18:42:22 -03:00
/* AI Panel */
.ai-panel {
position: absolute;
right: 0;
top: 53px;
bottom: 32px;
width: 360px;
background: var(--card);
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
z-index: 50;
transform: translateX(0);
transition: transform 0.2s ease;
2025-12-03 18:42:22 -03:00
}
.ai-panel.hidden {
transform: translateX(100%);
}
2025-12-03 18:42:22 -03:00
.ai-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid var(--border);
}
2025-12-03 18:42:22 -03:00
.ai-panel-header h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
2025-12-03 18:42:22 -03:00
.ai-panel-content {
flex: 1;
overflow-y: auto;
padding: 16px;
2025-12-03 18:42:22 -03:00
}
.ai-quick-actions {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-bottom: 20px;
}
2025-12-03 18:42:22 -03:00
.ai-action-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--background);
color: var(--foreground);
font-size: 13px;
cursor: pointer;
transition: all 0.15s;
}
2025-12-03 18:42:22 -03:00
.ai-action-btn:hover {
background: var(--accent);
border-color: var(--primary);
}
2025-12-03 18:42:22 -03:00
.ai-tone-section,
.ai-translate-section,
.ai-custom-section {
margin-bottom: 20px;
2025-12-03 18:42:22 -03:00
}
.ai-tone-section label,
.ai-translate-section label,
.ai-custom-section label {
display: block;
font-size: 12px;
font-weight: 600;
color: var(--muted-foreground);
margin-bottom: 8px;
2025-12-03 18:42:22 -03:00
}
.tone-buttons {
display: flex;
flex-wrap: wrap;
gap: 6px;
2025-12-03 18:42:22 -03:00
}
.tone-btn {
padding: 6px 12px;
border: 1px solid var(--border);
border-radius: 16px;
background: var(--background);
color: var(--foreground);
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
}
.tone-btn:hover,
.tone-btn.active {
background: var(--primary);
color: var(--primary-foreground);
border-color: var(--primary);
}
.ai-select {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--background);
color: var(--foreground);
font-size: 14px;
margin-bottom: 8px;
}
.ai-custom-section textarea {
width: 100%;
padding: 12px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--background);
color: var(--foreground);
font-size: 14px;
resize: vertical;
min-height: 80px;
margin-bottom: 8px;
}
.btn-primary {
width: 100%;
padding: 10px 16px;
border: none;
border-radius: 6px;
background: var(--primary);
color: var(--primary-foreground);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: opacity 0.15s;
}
.btn-primary:hover {
opacity: 0.9;
2025-12-03 18:42:22 -03:00
}
.ai-response {
border-top: 1px solid var(--border);
padding: 16px;
background: var(--muted);
2025-12-03 18:42:22 -03:00
}
.ai-response.hidden {
display: none;
}
.ai-response-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.ai-response-header span {
font-size: 12px;
font-weight: 600;
color: var(--muted-foreground);
}
.ai-response-actions {
display: flex;
gap: 4px;
}
.ai-response-content {
font-size: 14px;
line-height: 1.6;
padding: 12px;
background: var(--card);
border-radius: 6px;
max-height: 200px;
overflow-y: auto;
}
/* Status Bar */
.paper-status-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 16px;
border-top: 1px solid var(--border);
background: var(--card);
font-size: 12px;
color: var(--muted-foreground);
}
.status-left,
.status-center,
.status-right {
display: flex;
align-items: center;
gap: 16px;
}
.save-status.saving {
color: var(--chart-4);
}
.save-status.saved {
color: var(--chart-2);
}
/* Modal */
.modal {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 200;
}
.modal.hidden {
display: none;
}
.modal-content {
width: 90%;
max-width: 480px;
background: var(--card);
border-radius: 12px;
overflow: hidden;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
}
.modal-header h3 {
margin: 0;
font-size: 18px;
}
.modal-body {
padding: 20px;
}
.export-options {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.export-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 20px 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--background);
color: var(--foreground);
cursor: pointer;
transition: all 0.15s;
}
.export-option:hover {
background: var(--accent);
border-color: var(--primary);
}
.export-icon {
font-size: 28px;
}
2025-12-03 18:42:22 -03:00
.export-label {
font-size: 13px;
font-weight: 500;
2025-12-03 18:42:22 -03:00
}
/* Responsive */
@media (max-width: 768px) {
.paper-sidebar {
position: absolute;
left: 0;
top: 0;
bottom: 0;
z-index: 60;
transform: translateX(-100%);
}
.paper-sidebar.open {
transform: translateX(0);
}
.editor-canvas {
padding: 20px;
}
.paper-page {
padding: 30px 20px;
}
.ai-panel {
width: 100%;
}
.toolbar-center {
display: none;
}
}
</style>
2025-12-03 18:42:22 -03:00
<script>
(function () {
const editor = document.getElementById("editor-content");
const title = document.getElementById("paper-title");
const slashMenu = document.getElementById("slash-menu");
const aiPanel = document.getElementById("ai-panel");
const sidebar = document.getElementById("paper-sidebar");
// Slash command handling
let slashPosition = null;
editor.addEventListener("input", function (e) {
updateWordCount();
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const text = range.startContainer.textContent || "";
const cursorPos = range.startOffset;
// Check for slash command
if (text[cursorPos - 1] === "/") {
showSlashMenu(range);
} else if (slashMenu && !slashMenu.classList.contains("hidden")) {
// Filter slash menu based on input after /
const slashIndex = text.lastIndexOf("/");
if (slashIndex >= 0 && cursorPos > slashIndex) {
const filter = text
.substring(slashIndex + 1, cursorPos)
.toLowerCase();
filterSlashMenu(filter);
}
2025-12-03 18:42:22 -03:00
}
});
editor.addEventListener("keydown", function (e) {
// Handle slash menu navigation
if (!slashMenu.classList.contains("hidden")) {
if (e.key === "Escape") {
hideSlashMenu();
e.preventDefault();
} else if (e.key === "Enter") {
const selected =
slashMenu.querySelector(".slash-item.selected") ||
slashMenu.querySelector(".slash-item");
if (selected) {
executeSlashCommand(selected.dataset.cmd);
e.preventDefault();
}
} else if (e.key === "ArrowDown" || e.key === "ArrowUp") {
navigateSlashMenu(e.key === "ArrowDown" ? 1 : -1);
e.preventDefault();
}
}
// Keyboard shortcuts
if (e.ctrlKey || e.metaKey) {
switch (e.key.toLowerCase()) {
case "b":
e.preventDefault();
document.execCommand("bold");
break;
case "i":
e.preventDefault();
document.execCommand("italic");
break;
case "u":
e.preventDefault();
document.execCommand("underline");
break;
case "s":
e.preventDefault();
saveDocument();
break;
}
}
});
function showSlashMenu(range) {
const rect = range.getBoundingClientRect();
const editorRect = editor.getBoundingClientRect();
slashMenu.style.top =
rect.bottom - editorRect.top + editor.scrollTop + 8 + "px";
slashMenu.style.left = rect.left - editorRect.left + "px";
slashMenu.classList.remove("hidden");
slashPosition = range.startOffset;
}
function hideSlashMenu() {
slashMenu.classList.add("hidden");
slashPosition = null;
}
function filterSlashMenu(filter) {
const items = slashMenu.querySelectorAll(".slash-item");
let firstVisible = null;
items.forEach((item) => {
const label = item
.querySelector(".slash-label")
.textContent.toLowerCase();
const matches = label.includes(filter);
item.style.display = matches ? "flex" : "none";
if (matches && !firstVisible) firstVisible = item;
});
// Select first visible
items.forEach((item) => item.classList.remove("selected"));
if (firstVisible) firstVisible.classList.add("selected");
}
function navigateSlashMenu(direction) {
const items = Array.from(
slashMenu.querySelectorAll(".slash-item"),
).filter((i) => i.style.display !== "none");
const current = items.findIndex((i) =>
i.classList.contains("selected"),
);
items.forEach((i) => i.classList.remove("selected"));
let next = current + direction;
if (next < 0) next = items.length - 1;
if (next >= items.length) next = 0;
items[next]?.classList.add("selected");
items[next]?.scrollIntoView({ block: "nearest" });
}
function executeSlashCommand(cmd) {
hideSlashMenu();
// Remove the slash character
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const text = range.startContainer.textContent;
const slashIndex = text.lastIndexOf("/");
if (slashIndex >= 0) {
range.startContainer.textContent =
text.substring(0, slashIndex) +
text.substring(range.startOffset);
range.setStart(range.startContainer, slashIndex);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
// Execute command
switch (cmd) {
case "h1":
document.execCommand("formatBlock", false, "h1");
2025-12-03 18:42:22 -03:00
break;
case "h2":
document.execCommand("formatBlock", false, "h2");
2025-12-03 18:42:22 -03:00
break;
case "h3":
document.execCommand("formatBlock", false, "h3");
2025-12-03 18:42:22 -03:00
break;
case "bullet":
document.execCommand("insertUnorderedList");
2025-12-03 18:42:22 -03:00
break;
case "number":
document.execCommand("insertOrderedList");
2025-12-03 18:42:22 -03:00
break;
case "todo":
2025-12-03 18:42:22 -03:00
insertTodo();
break;
case "quote":
document.execCommand("formatBlock", false, "blockquote");
2025-12-03 18:42:22 -03:00
break;
case "code":
document.execCommand("formatBlock", false, "pre");
2025-12-03 18:42:22 -03:00
break;
case "divider":
document.execCommand("insertHTML", false, "<hr>");
2025-12-03 18:42:22 -03:00
break;
case "callout":
document.execCommand(
"insertHTML",
false,
'<div class="callout">💡 </div>',
);
2025-12-03 18:42:22 -03:00
break;
case "table":
insertTable();
2025-12-03 18:42:22 -03:00
break;
case "image":
insertImage();
2025-12-03 18:42:22 -03:00
break;
case "ai-write":
case "ai-summarize":
case "ai-expand":
case "ai-improve":
case "ai-translate":
case "ai-extract":
openAIPanel(cmd);
2025-12-03 18:42:22 -03:00
break;
}
}
function insertTodo() {
const html =
'<div class="todo-item"><input type="checkbox" class="todo-checkbox"><span></span></div>';
document.execCommand("insertHTML", false, html);
}
function insertTable() {
const html = `
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="border: 1px solid var(--border); padding: 8px;"></td>
<td style="border: 1px solid var(--border); padding: 8px;"></td>
<td style="border: 1px solid var(--border); padding: 8px;"></td>
</tr>
<tr>
<td style="border: 1px solid var(--border); padding: 8px;"></td>
<td style="border: 1px solid var(--border); padding: 8px;"></td>
<td style="border: 1px solid var(--border); padding: 8px;"></td>
</tr>
</table>
`;
document.execCommand("insertHTML", false, html);
}
function insertImage() {
const url = prompt("Enter image URL:");
if (url) {
document.execCommand(
"insertHTML",
false,
`<img src="${url}" style="max-width: 100%;">`,
);
2025-12-03 18:42:22 -03:00
}
}
2025-12-03 18:42:22 -03:00
function openAIPanel(action) {
const selectedText = window.getSelection().toString();
document.getElementById("selected-text-input").value = selectedText;
aiPanel.classList.remove("hidden");
}
function updateWordCount() {
const text = editor.innerText || "";
const words = text
.trim()
.split(/\s+/)
.filter((w) => w.length > 0).length;
const chars = text.length;
const wordEl = document.getElementById("word-count");
const charEl = document.getElementById("char-count");
if (window.i18n && window.i18n.loaded) {
wordEl.textContent = window.i18n.t("paper-word-count", {
count: words,
});
charEl.textContent = window.i18n.t("paper-char-count", {
count: chars,
});
} else {
wordEl.textContent = words + " words";
charEl.textContent = chars + " characters";
}
}
function saveDocument() {
const status = document.getElementById("save-status");
const savingText =
window.i18n && window.i18n.loaded
? window.i18n.t("paper-saving")
: "Saving...";
const savedText =
window.i18n && window.i18n.loaded
? window.i18n.t("paper-saved")
: "Saved";
status.textContent = savingText;
status.className = "status-item save-status saving";
htmx.ajax("POST", "/api/ui/paper/save", {
target: "none",
values: {
title: title.innerText,
content: editor.innerHTML,
},
}).then(() => {
status.textContent = savedText;
status.className = "status-item save-status saved";
});
2025-12-03 18:42:22 -03:00
}
// Toolbar commands
document.querySelectorAll("[data-cmd]").forEach((btn) => {
btn.addEventListener("click", function () {
const cmd = this.dataset.cmd;
switch (cmd) {
case "bold":
document.execCommand("bold");
break;
case "italic":
document.execCommand("italic");
break;
case "underline":
document.execCommand("underline");
break;
case "strikethrough":
document.execCommand("strikeThrough");
break;
case "highlight":
document.execCommand("hiliteColor", false, "#ffff00");
break;
case "alignLeft":
document.execCommand("justifyLeft");
break;
case "alignCenter":
document.execCommand("justifyCenter");
break;
case "alignRight":
document.execCommand("justifyRight");
break;
case "bulletList":
document.execCommand("insertUnorderedList");
break;
case "numberedList":
document.execCommand("insertOrderedList");
break;
case "todoList":
insertTodo();
break;
case "link":
const url = prompt("Enter URL:");
if (url) document.execCommand("createLink", false, url);
break;
case "image":
insertImage();
break;
case "table":
insertTable();
break;
case "code":
document.execCommand("formatBlock", false, "pre");
break;
case "quote":
document.execCommand(
"formatBlock",
false,
"blockquote",
);
break;
case "undo":
document.execCommand("undo");
break;
case "redo":
document.execCommand("redo");
break;
}
editor.focus();
});
2025-12-03 18:42:22 -03:00
});
// Heading select
document
.getElementById("heading-select")
.addEventListener("change", function () {
const value = this.value;
if (value === "p") {
document.execCommand("formatBlock", false, "p");
} else {
document.execCommand("formatBlock", false, value);
}
editor.focus();
});
// Toggle sidebar
document
.getElementById("toggle-sidebar")
.addEventListener("click", function () {
sidebar.classList.toggle("collapsed");
});
2025-12-03 18:42:22 -03:00
// AI Panel
document
.getElementById("ai-assist-btn")
.addEventListener("click", function () {
const selectedText = window.getSelection().toString();
document.getElementById("selected-text-input").value =
selectedText;
aiPanel.classList.toggle("hidden");
});
2025-12-03 18:42:22 -03:00
document
.getElementById("close-ai-panel")
.addEventListener("click", function () {
aiPanel.classList.add("hidden");
});
// Export modal
document
.getElementById("export-btn")
.addEventListener("click", function () {
document
.getElementById("export-modal")
.classList.remove("hidden");
});
document.querySelectorAll(".close-modal").forEach((btn) => {
btn.addEventListener("click", function () {
this.closest(".modal").classList.add("hidden");
});
});
// Click outside modal to close
document.querySelectorAll(".modal").forEach((modal) => {
modal.addEventListener("click", function (e) {
if (e.target === this) {
this.classList.add("hidden");
2025-12-03 18:42:22 -03:00
}
});
});
// Click outside slash menu to close
document.addEventListener("click", function (e) {
if (!slashMenu.contains(e.target) && !editor.contains(e.target)) {
hideSlashMenu();
}
});
// Slash menu item click
slashMenu.querySelectorAll(".slash-item").forEach((item) => {
item.addEventListener("click", function () {
executeSlashCommand(this.dataset.cmd);
});
});
// Tone buttons
document.querySelectorAll(".tone-btn").forEach((btn) => {
btn.addEventListener("click", function () {
document
.querySelectorAll(".tone-btn")
.forEach((b) => b.classList.remove("active"));
this.classList.add("active");
const tone = this.dataset.tone;
const selectedText = document.getElementById(
"selected-text-input",
).value;
htmx.ajax("POST", "/api/ui/paper/ai/tone", {
target: "#ai-response-content",
values: {
tone: tone,
text: selectedText,
},
}).then(() => {
document
.getElementById("ai-response")
.classList.remove("hidden");
});
});
});
// AI response actions
document
.getElementById("copy-ai-response")
?.addEventListener("click", function () {
const content = document.getElementById(
"ai-response-content",
).innerText;
navigator.clipboard.writeText(content);
});
document
.getElementById("insert-ai-response")
?.addEventListener("click", function () {
const content = document.getElementById(
"ai-response-content",
).innerHTML;
editor.focus();
document.execCommand("insertHTML", false, content);
});
document
.getElementById("replace-ai-response")
?.addEventListener("click", function () {
const content = document.getElementById(
"ai-response-content",
).innerHTML;
document.execCommand("insertHTML", false, content);
});
// Initial word count
updateWordCount();
})();
2025-12-03 18:42:22 -03:00
</script>