Add standalone /editor-poc page demonstrating all 5 toolbar zones: - Zone 1: Top Fixed Bar (hamburger, title, AI glow button, save indicator) - Zone 2a: Slash Command Menu (/ on empty line, 15 commands, filtering) - Zone 2b: Bubble Menu (on text selection, formatting + AI dropdown) - Zone 3: Floating Quick Insert (per-line, Image/PDF/Code/Table/AI) - Zone 4: AI Side Panel (Ctrl+J toggle, chat, suggestions) - Zone 5: Drag & Drop overlay with dashed border Files: - new: frontend/editor-poc.html (46KB standalone vanilla JS) - backend/main.py: +/editor-poc route before catch-all - frontend/index.html: Editor POC button in header menu - frontend/js/config.js: initEditorPocBtn() function + export - frontend/js/legacy.js: re-export initEditorPocBtn - frontend/js/app.js + frontend/app.js: init call in both versions
1225 lines
46 KiB
HTML
1225 lines
46 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>ObsiGate — Editor POC (Multi-Zone Toolbar)</title>
|
|
<style>
|
|
/* ==========================================================================
|
|
ObsiGate Editor POC — Dark Theme CSS Variables
|
|
========================================================================== */
|
|
:root {
|
|
--bg-primary: #0f1117;
|
|
--bg-secondary: #161822;
|
|
--bg-tertiary: #1c1f2e;
|
|
--bg-elevated: #222639;
|
|
--bg-hover: #2a2d3e;
|
|
--text-primary: #e1e4ed;
|
|
--text-secondary: #9ca3b8;
|
|
--text-muted: #6b7280;
|
|
--border: #2d3148;
|
|
--border-light: #3a3f58;
|
|
--accent: #4a7ab5;
|
|
--accent-glow: rgba(74, 122, 181, 0.4);
|
|
--accent-bg: rgba(74, 122, 181, 0.12);
|
|
--accent-ai: #8b5cf6;
|
|
--accent-ai-glow: rgba(139, 92, 246, 0.4);
|
|
--accent-ai-bg: rgba(139, 92, 246, 0.12);
|
|
--success: #34d399;
|
|
--warning: #fbbf24;
|
|
--error: #f87171;
|
|
--radius-sm: 6px;
|
|
--radius: 8px;
|
|
--radius-lg: 12px;
|
|
--shadow-popover: 0 8px 32px rgba(0,0,0,0.4);
|
|
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
--font-mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: var(--font-sans);
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
ZONE 1 — Top Fixed Bar
|
|
========================================================================== */
|
|
.poc-topbar {
|
|
height: 48px;
|
|
min-height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 0 16px;
|
|
background: var(--bg-secondary);
|
|
border-bottom: 1px solid var(--border);
|
|
z-index: 100;
|
|
}
|
|
.poc-topbar-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.poc-hamburger {
|
|
width: 32px; height: 32px;
|
|
border-radius: var(--radius-sm);
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 18px;
|
|
}
|
|
.poc-hamburger:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
.poc-brand {
|
|
font-weight: 700; font-size: 13px;
|
|
color: var(--accent); white-space: nowrap;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.poc-title-input {
|
|
flex: 1; min-width: 100px;
|
|
background: transparent; border: 1px solid transparent;
|
|
color: var(--text-primary); font-size: 14px;
|
|
padding: 4px 8px; border-radius: var(--radius-sm);
|
|
outline: none; font-family: var(--font-sans);
|
|
}
|
|
.poc-title-input:hover { border-color: var(--border); }
|
|
.poc-title-input:focus { border-color: var(--accent); background: var(--bg-tertiary); }
|
|
.poc-topbar-right {
|
|
display: flex; align-items: center; gap: 6px;
|
|
}
|
|
.poc-btn {
|
|
height: 32px; padding: 0 12px;
|
|
border-radius: var(--radius-sm); border: 1px solid transparent;
|
|
background: transparent; color: var(--text-secondary);
|
|
cursor: pointer; font-size: 13px;
|
|
display: flex; align-items: center; gap: 6px;
|
|
white-space: nowrap; font-family: var(--font-sans);
|
|
transition: all 120ms ease;
|
|
}
|
|
.poc-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
.poc-btn-ai {
|
|
background: var(--accent-ai-bg); color: var(--accent-ai);
|
|
border-color: rgba(139, 92, 246, 0.25);
|
|
font-weight: 600;
|
|
box-shadow: 0 0 12px var(--accent-ai-glow);
|
|
}
|
|
.poc-btn-ai:hover {
|
|
background: rgba(139, 92, 246, 0.2);
|
|
box-shadow: 0 0 20px var(--accent-ai-glow);
|
|
}
|
|
.poc-btn-ai.active {
|
|
background: var(--accent-ai); color: #fff;
|
|
}
|
|
.poc-save-indicator {
|
|
display: flex; align-items: center; gap: 5px;
|
|
font-size: 12px; color: var(--success);
|
|
padding: 0 4px;
|
|
}
|
|
.poc-save-dot {
|
|
width: 7px; height: 7px; border-radius: 50%;
|
|
background: var(--success);
|
|
}
|
|
|
|
/* ==========================================================================
|
|
ZONE 4 — AI Side Panel
|
|
========================================================================== */
|
|
.poc-workspace {
|
|
display: flex; flex: 1; overflow: hidden;
|
|
}
|
|
.poc-editor-area {
|
|
flex: 1; overflow-y: auto; overflow-x: hidden;
|
|
transition: margin-right 200ms ease;
|
|
position: relative;
|
|
}
|
|
.poc-ai-panel {
|
|
width: 340px; min-width: 340px;
|
|
background: var(--bg-secondary);
|
|
border-left: 1px solid var(--border);
|
|
display: flex; flex-direction: column;
|
|
transform: translateX(0); transition: transform 200ms ease, min-width 200ms ease, width 200ms ease;
|
|
overflow: hidden;
|
|
}
|
|
.poc-ai-panel.hidden {
|
|
transform: translateX(100%); min-width: 0; width: 0; border-left: none;
|
|
}
|
|
.poc-ai-panel-header {
|
|
padding: 14px 16px; border-bottom: 1px solid var(--border);
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
}
|
|
.poc-ai-panel-title {
|
|
font-weight: 700; font-size: 14px; color: var(--accent-ai);
|
|
display: flex; align-items: center; gap: 6px;
|
|
}
|
|
.poc-ai-panel-close {
|
|
width: 28px; height: 28px; border-radius: var(--radius-sm);
|
|
border: none; background: transparent; color: var(--text-muted);
|
|
cursor: pointer; font-size: 16px;
|
|
}
|
|
.poc-ai-panel-close:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
.poc-ai-chat {
|
|
flex: 1; padding: 16px; overflow-y: auto;
|
|
display: flex; flex-direction: column; gap: 12px;
|
|
}
|
|
.poc-ai-suggestions {
|
|
padding: 12px 16px; border-top: 1px solid var(--border);
|
|
}
|
|
.poc-ai-suggestion {
|
|
display: block; width: 100%; text-align: left;
|
|
padding: 8px 12px; margin-bottom: 4px;
|
|
border-radius: var(--radius-sm); border: none;
|
|
background: transparent; color: var(--text-secondary);
|
|
cursor: pointer; font-size: 13px; font-family: var(--font-sans);
|
|
}
|
|
.poc-ai-suggestion:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
.poc-ai-input-row {
|
|
padding: 10px 16px; border-top: 1px solid var(--border);
|
|
display: flex; gap: 8px;
|
|
}
|
|
.poc-ai-input {
|
|
flex: 1; padding: 8px 12px;
|
|
border-radius: var(--radius-sm); border: 1px solid var(--border);
|
|
background: var(--bg-tertiary); color: var(--text-primary);
|
|
font-size: 13px; outline: none; font-family: var(--font-sans);
|
|
}
|
|
.poc-ai-input:focus { border-color: var(--accent-ai); }
|
|
.poc-ai-send {
|
|
padding: 8px 14px; border-radius: var(--radius-sm);
|
|
border: none; background: var(--accent-ai); color: #fff;
|
|
cursor: pointer; font-size: 13px; font-weight: 600;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Editor Content Area
|
|
========================================================================== */
|
|
.poc-editor {
|
|
max-width: 800px; margin: 40px auto; padding: 0 40px 200px 60px;
|
|
font-size: 15px; line-height: 1.75; color: var(--text-primary);
|
|
outline: none; min-height: 600px;
|
|
position: relative;
|
|
}
|
|
.poc-editor h1 { font-size: 28px; margin: 24px 0 12px; color: var(--text-primary); }
|
|
.poc-editor h2 { font-size: 22px; margin: 20px 0 8px; color: var(--text-primary); }
|
|
.poc-editor p { margin: 8px 0; }
|
|
.poc-editor .line {
|
|
position: relative; padding: 4px 0; border-radius: 4px;
|
|
min-height: 28px;
|
|
}
|
|
.poc-editor .line:hover { background: rgba(255,255,255,0.015); }
|
|
.poc-editor .line.empty { color: var(--text-muted); font-style: italic; }
|
|
|
|
/* ==========================================================================
|
|
ZONE 3 — Floating Quick Insert
|
|
========================================================================== */
|
|
.poc-quick-insert {
|
|
position: absolute; left: -34px;
|
|
width: 24px; height: 24px;
|
|
border-radius: 50%; border: 1px solid transparent;
|
|
background: transparent; color: transparent;
|
|
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
font-size: 16px; font-weight: 700;
|
|
transition: all 100ms ease;
|
|
top: 50%; transform: translateY(-50%);
|
|
z-index: 10;
|
|
}
|
|
.poc-editor .line:hover .poc-quick-insert,
|
|
.poc-editor .line.empty .poc-quick-insert {
|
|
color: var(--text-muted); border-color: var(--border);
|
|
background: var(--bg-secondary);
|
|
}
|
|
.poc-editor .line:hover .poc-quick-insert:hover {
|
|
color: var(--text-primary); border-color: var(--accent);
|
|
background: var(--accent-bg);
|
|
}
|
|
|
|
/* ==========================================================================
|
|
ZONE 2a — Slash Command Menu
|
|
========================================================================== */
|
|
.poc-slash-menu {
|
|
position: absolute; z-index: 200;
|
|
width: 320px; max-height: 380px; overflow-y: auto;
|
|
background: var(--bg-elevated);
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow-popover);
|
|
display: none;
|
|
}
|
|
.poc-slash-menu.visible { display: block; animation: pocFadeSlideIn 120ms ease; }
|
|
@keyframes pocFadeSlideIn {
|
|
from { opacity: 0; transform: translateY(-6px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
.poc-slash-search {
|
|
padding: 10px 12px; border-bottom: 1px solid var(--border);
|
|
position: sticky; top: 0; background: var(--bg-elevated);
|
|
border-radius: var(--radius) var(--radius) 0 0;
|
|
}
|
|
.poc-slash-search input {
|
|
width: 100%; padding: 6px 10px;
|
|
border-radius: var(--radius-sm); border: 1px solid var(--border);
|
|
background: var(--bg-tertiary); color: var(--text-primary);
|
|
font-size: 13px; outline: none; font-family: var(--font-sans);
|
|
}
|
|
.poc-slash-search input:focus { border-color: var(--accent); }
|
|
.poc-slash-section-title {
|
|
padding: 8px 14px 4px; font-size: 11px; font-weight: 600;
|
|
color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px;
|
|
}
|
|
.poc-slash-item {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 8px 14px; cursor: pointer;
|
|
font-size: 13px; color: var(--text-secondary);
|
|
transition: background 80ms ease;
|
|
}
|
|
.poc-slash-item:hover, .poc-slash-item.active {
|
|
background: var(--bg-hover); color: var(--text-primary);
|
|
}
|
|
.poc-slash-item-icon {
|
|
width: 28px; height: 28px; border-radius: var(--radius-sm);
|
|
background: var(--bg-tertiary); display: flex;
|
|
align-items: center; justify-content: center;
|
|
font-size: 15px; flex-shrink: 0;
|
|
}
|
|
.poc-slash-item-body { flex: 1; min-width: 0; }
|
|
.poc-slash-item-title { font-weight: 600; font-size: 13px; color: var(--text-primary); }
|
|
.poc-slash-item-desc { font-size: 11px; color: var(--text-muted); margin-top: 1px; }
|
|
.poc-slash-divider {
|
|
height: 1px; margin: 4px 14px; background: var(--border);
|
|
}
|
|
|
|
/* ==========================================================================
|
|
ZONE 2b — Bubble Menu
|
|
========================================================================== */
|
|
.poc-bubble-menu {
|
|
position: absolute; z-index: 150;
|
|
display: none; align-items: center; gap: 2px;
|
|
padding: 4px 6px;
|
|
background: var(--bg-elevated);
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow-popover);
|
|
}
|
|
.poc-bubble-menu.visible {
|
|
display: flex;
|
|
animation: pocFadeScaleIn 120ms ease;
|
|
}
|
|
@keyframes pocFadeScaleIn {
|
|
from { opacity: 0; transform: translateY(4px) scale(0.95); }
|
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
}
|
|
.poc-bubble-btn {
|
|
width: 30px; height: 30px; border-radius: var(--radius-sm);
|
|
border: none; background: transparent; color: var(--text-secondary);
|
|
cursor: pointer; font-size: 14px; display: flex;
|
|
align-items: center; justify-content: center;
|
|
transition: all 100ms ease;
|
|
}
|
|
.poc-bubble-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
.poc-bubble-btn.active { background: var(--accent-bg); color: var(--accent); }
|
|
.poc-bubble-sep {
|
|
width: 1px; height: 20px; background: var(--border);
|
|
margin: 0 4px;
|
|
}
|
|
.poc-bubble-btn-ai { color: var(--accent-ai); }
|
|
.poc-bubble-btn-ai:hover { background: var(--accent-ai-bg); }
|
|
.poc-bubble-more-menu {
|
|
position: absolute; top: 100%; right: 0; margin-top: 6px;
|
|
width: 200px; background: var(--bg-elevated);
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow-popover);
|
|
display: none; z-index: 160;
|
|
padding: 4px;
|
|
}
|
|
.poc-bubble-more-menu.visible { display: block; }
|
|
.poc-bubble-more-item {
|
|
display: flex; align-items: center; gap: 8px;
|
|
padding: 7px 10px; font-size: 13px; color: var(--text-secondary);
|
|
cursor: pointer; border-radius: var(--radius-sm);
|
|
}
|
|
.poc-bubble-more-item:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
|
|
/* ==========================================================================
|
|
ZONE 5 — Drag & Drop
|
|
========================================================================== */
|
|
.poc-drop-overlay {
|
|
position: fixed; inset: 0; z-index: 500;
|
|
display: none; align-items: center; justify-content: center;
|
|
background: rgba(15, 17, 23, 0.85);
|
|
border: 3px dashed var(--accent);
|
|
pointer-events: none;
|
|
}
|
|
.poc-drop-overlay.visible { display: flex; }
|
|
.poc-drop-message {
|
|
text-align: center; color: var(--accent);
|
|
}
|
|
.poc-drop-icon { font-size: 48px; margin-bottom: 12px; }
|
|
.poc-drop-title { font-size: 20px; font-weight: 700; margin-bottom: 6px; }
|
|
.poc-drop-subtitle { font-size: 14px; color: var(--text-secondary); }
|
|
|
|
/* ==========================================================================
|
|
Status Bar (Footer)
|
|
========================================================================== */
|
|
.poc-statusbar {
|
|
height: 28px; min-height: 28px;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 0 16px;
|
|
background: var(--bg-secondary);
|
|
border-top: 1px solid var(--border);
|
|
font-size: 11px; color: var(--text-muted);
|
|
}
|
|
.poc-statusbar-left, .poc-statusbar-right {
|
|
display: flex; align-items: center; gap: 16px;
|
|
}
|
|
.poc-status-ai-active {
|
|
color: var(--accent-ai); font-weight: 600;
|
|
display: none; align-items: center; gap: 4px;
|
|
}
|
|
.poc-status-ai-active.visible { display: flex; }
|
|
|
|
/* ==========================================================================
|
|
Toast
|
|
========================================================================== */
|
|
.poc-toast {
|
|
position: fixed; bottom: 48px; left: 50%; transform: translateX(-50%);
|
|
background: var(--bg-elevated); border: 1px solid var(--border-light);
|
|
border-radius: var(--radius); padding: 10px 20px;
|
|
font-size: 13px; color: var(--text-primary);
|
|
box-shadow: var(--shadow-popover);
|
|
z-index: 600; display: none;
|
|
animation: pocFadeSlideIn 150ms ease;
|
|
}
|
|
.poc-toast.visible { display: block; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- ======================================================================
|
|
ZONE 1 — Top Fixed Bar
|
|
====================================================================== -->
|
|
<div class="poc-topbar" id="poc-topbar">
|
|
<div class="poc-topbar-left">
|
|
<button class="poc-hamburger" title="Menu">☰</button>
|
|
<span class="poc-brand">ObsiGate</span>
|
|
<input class="poc-title-input" id="poc-title" value="Rapport d'analyse trimestriel"
|
|
placeholder="Titre du document..." spellcheck="false">
|
|
</div>
|
|
<div class="poc-topbar-right">
|
|
<button class="poc-btn poc-btn-ai" id="poc-btn-ai" title="AI Assistant (Ctrl+J)">
|
|
🔮 AI
|
|
</button>
|
|
<button class="poc-btn" id="poc-btn-insert" title="Insertion rapide">
|
|
📎 Insert
|
|
</button>
|
|
<button class="poc-btn" title="Actions">⚙</button>
|
|
<div class="poc-save-indicator" id="poc-save-indicator">
|
|
<span class="poc-save-dot"></span> Saved
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======================================================================
|
|
ZONE 4 + Editor Workspace
|
|
====================================================================== -->
|
|
<div class="poc-workspace">
|
|
<div class="poc-editor-area" id="poc-editor-area">
|
|
|
|
<!-- ==================================================================
|
|
ZONE 5 — Drag & Drop Overlay
|
|
================================================================== -->
|
|
<div class="poc-drop-overlay" id="poc-drop-overlay">
|
|
<div class="poc-drop-message">
|
|
<div class="poc-drop-icon">🖼</div>
|
|
<div class="poc-drop-title">Drop files here</div>
|
|
<div class="poc-drop-subtitle">Images, PDFs, documents</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================================================================
|
|
Editor Content
|
|
================================================================== -->
|
|
<div class="poc-editor" id="poc-editor" contenteditable="true" spellcheck="true">
|
|
<h1>Introduction</h1>
|
|
<div class="line"><p>This document demonstrates the <strong>multi-zone toolbar architecture</strong> proposed for ObsiGate's next-generation editor. The design draws inspiration from Notion, Medium, Dropbox Paper, and Cursor.</p></div>
|
|
<div class="line"><p></p></div>
|
|
<h2>Key Principles</h2>
|
|
<div class="line"><p>Modern editors no longer use a single fixed toolbar. Controls are distributed across <strong>5 contextual zones</strong> that appear only when relevant.</p></div>
|
|
<div class="line"><p>The core principle: <em>"Show the right control, in the right place, at the right time."</em></p></div>
|
|
<div class="line"><p></p></div>
|
|
<div class="line"><p></p></div>
|
|
</div>
|
|
|
|
<!-- ==================================================================
|
|
ZONE 2a — Slash Command Menu
|
|
================================================================== -->
|
|
<div class="poc-slash-menu" id="poc-slash-menu">
|
|
<div class="poc-slash-search">
|
|
<input type="text" id="poc-slash-search-input" placeholder="Filter commands..." autocomplete="off">
|
|
</div>
|
|
<div class="poc-slash-section-title">Formatting</div>
|
|
<div class="poc-slash-item" data-action="text">
|
|
<span class="poc-slash-item-icon">📝</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Text</div>
|
|
<div class="poc-slash-item-desc">Just start writing plain text</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="h1">
|
|
<span class="poc-slash-item-icon">H1</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Heading 1</div>
|
|
<div class="poc-slash-item-desc">Big section heading</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="h2">
|
|
<span class="poc-slash-item-icon">H2</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Heading 2</div>
|
|
<div class="poc-slash-item-desc">Medium section heading</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="bullet">
|
|
<span class="poc-slash-item-icon">•</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Bullet List</div>
|
|
<div class="poc-slash-item-desc">Simple unordered list</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="numbered">
|
|
<span class="poc-slash-item-icon">1.</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Numbered List</div>
|
|
<div class="poc-slash-item-desc">Ordered list</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="quote">
|
|
<span class="poc-slash-item-icon">❝</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Quote</div>
|
|
<div class="poc-slash-item-desc">Block quote section</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="code">
|
|
<span class="poc-slash-item-icon"></></span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Code Block</div>
|
|
<div class="poc-slash-item-desc">Syntax-highlighted code</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-divider"></div>
|
|
<div class="poc-slash-section-title">Insert</div>
|
|
<div class="poc-slash-item" data-action="image">
|
|
<span class="poc-slash-item-icon">🎨</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Insert Image</div>
|
|
<div class="poc-slash-item-desc">Upload or embed an image</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="pdf">
|
|
<span class="poc-slash-item-icon">📄</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Insert PDF</div>
|
|
<div class="poc-slash-item-desc">Attach a PDF document</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="table">
|
|
<span class="poc-slash-item-icon">📊</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">Insert Table</div>
|
|
<div class="poc-slash-item-desc">Data table</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-divider"></div>
|
|
<div class="poc-slash-section-title">AI Actions</div>
|
|
<div class="poc-slash-item" data-action="ai-continue">
|
|
<span class="poc-slash-item-icon" style="color:var(--accent-ai)">✨</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">AI: Continue writing</div>
|
|
<div class="poc-slash-item-desc">Let AI continue from here</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="ai-improve">
|
|
<span class="poc-slash-item-icon" style="color:var(--accent-ai)">✨</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">AI: Improve writing</div>
|
|
<div class="poc-slash-item-desc">Enhance style and clarity</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="ai-summarize">
|
|
<span class="poc-slash-item-icon" style="color:var(--accent-ai)">✨</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">AI: Summarize</div>
|
|
<div class="poc-slash-item-desc">Condense this document</div>
|
|
</div>
|
|
</div>
|
|
<div class="poc-slash-item" data-action="ai-translate">
|
|
<span class="poc-slash-item-icon" style="color:var(--accent-ai)">✨</span>
|
|
<div class="poc-slash-item-body">
|
|
<div class="poc-slash-item-title">AI: Translate</div>
|
|
<div class="poc-slash-item-desc">Translate to another language</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================================================================
|
|
ZONE 2b — Bubble Menu
|
|
================================================================== -->
|
|
<div class="poc-bubble-menu" id="poc-bubble-menu">
|
|
<button class="poc-bubble-btn" data-action="bold" title="Bold (Ctrl+B)"><b>B</b></button>
|
|
<button class="poc-bubble-btn" data-action="italic" title="Italic (Ctrl+I)"><i>I</i></button>
|
|
<button class="poc-bubble-btn" data-action="strike" title="Strikethrough"><s>S</s></button>
|
|
<button class="poc-bubble-btn" data-action="code" title="Inline Code"></></button>
|
|
<button class="poc-bubble-btn" data-action="link" title="Link (Ctrl+K)">🔗</button>
|
|
<span class="poc-bubble-sep"></span>
|
|
<button class="poc-bubble-btn poc-bubble-btn-ai" id="poc-bubble-more" title="More">⋯</button>
|
|
<button class="poc-bubble-btn poc-bubble-btn-ai" id="poc-bubble-ai" title="AI Actions">✨</button>
|
|
<div class="poc-bubble-more-menu" id="poc-bubble-more-menu">
|
|
<div class="poc-bubble-more-item" data-action="ai-improve">✨ Improve writing</div>
|
|
<div class="poc-bubble-more-item" data-action="ai-grammar">✅ Fix grammar</div>
|
|
<div class="poc-bubble-more-item" data-action="ai-translate">🌐 Translate to...</div>
|
|
<div class="poc-bubble-more-item" data-action="ai-continue">➕ Continue writing</div>
|
|
<div class="poc-bubble-more-item" data-action="ai-summarize">📝 Summarize</div>
|
|
<div class="poc-bubble-more-item" data-action="ai-tone">🎶 Change tone</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================================================================
|
|
ZONE 3 — Quick Insert buttons (injected by JS)
|
|
================================================================== -->
|
|
|
|
</div><!-- /poc-editor-area -->
|
|
|
|
<!-- ======================================================================
|
|
ZONE 4 — AI Side Panel
|
|
====================================================================== -->
|
|
<div class="poc-ai-panel hidden" id="poc-ai-panel">
|
|
<div class="poc-ai-panel-header">
|
|
<span class="poc-ai-panel-title">✨ AI Assistant</span>
|
|
<button class="poc-ai-panel-close" id="poc-ai-panel-close" title="Close panel">×</button>
|
|
</div>
|
|
<div class="poc-ai-chat" id="poc-ai-chat">
|
|
<div style="color:var(--text-muted);font-size:13px;padding:12px">
|
|
The AI assistant knows the content of your document. Ask questions, request summaries, or get writing suggestions.
|
|
</div>
|
|
</div>
|
|
<div class="poc-ai-suggestions">
|
|
<button class="poc-ai-suggestion">✨ Improve overall style</button>
|
|
<button class="poc-ai-suggestion">➕ Add a conclusion section</button>
|
|
<button class="poc-ai-suggestion">🌐 Translate to English</button>
|
|
<button class="poc-ai-suggestion">📄 Generate PDF summary</button>
|
|
</div>
|
|
<div class="poc-ai-input-row">
|
|
<input class="poc-ai-input" id="poc-ai-input" placeholder="Ask AI something...">
|
|
<button class="poc-ai-send" id="poc-ai-send">Send</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======================================================================
|
|
Status Bar
|
|
====================================================================== -->
|
|
<div class="poc-statusbar">
|
|
<div class="poc-statusbar-left">
|
|
<span>Type <strong>/</strong> for slash menu</span>
|
|
<span>Ctrl+J for AI panel</span>
|
|
<span class="poc-status-ai-active" id="poc-status-ai">✨ AI processing...</span>
|
|
</div>
|
|
<div class="poc-statusbar-right">
|
|
<span id="poc-word-count">42 words</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======================================================================
|
|
Toast notifications
|
|
====================================================================== -->
|
|
<div class="poc-toast" id="poc-toast"></div>
|
|
|
|
<!-- ======================================================================
|
|
JavaScript — All 5 Zones Implementation
|
|
====================================================================== -->
|
|
<script>
|
|
(function() {
|
|
'use strict';
|
|
|
|
// DOM references
|
|
var editor = document.getElementById('poc-editor');
|
|
var editorArea = document.getElementById('poc-editor-area');
|
|
var slashMenu = document.getElementById('poc-slash-menu');
|
|
var slashSearch = document.getElementById('poc-slash-search-input');
|
|
var bubbleMenu = document.getElementById('poc-bubble-menu');
|
|
var bubbleMoreMenu = document.getElementById('poc-bubble-more-menu');
|
|
var aiPanel = document.getElementById('poc-ai-panel');
|
|
var btnAI = document.getElementById('poc-btn-ai');
|
|
var dropOverlay = document.getElementById('poc-drop-overlay');
|
|
var saveIndicator = document.getElementById('poc-save-indicator');
|
|
var wordCount = document.getElementById('poc-word-count');
|
|
var aiStatus = document.getElementById('poc-status-ai');
|
|
var toast = document.getElementById('poc-toast');
|
|
|
|
var slashVisible = false;
|
|
var slashItems = [];
|
|
var slashActiveIdx = -1;
|
|
var bubbleVisible = false;
|
|
var saveTimer = null;
|
|
|
|
// =========================================================================
|
|
// Zone 2a — Slash Command Menu
|
|
// =========================================================================
|
|
|
|
function initSlashItems() {
|
|
var items = slashMenu.querySelectorAll('.poc-slash-item');
|
|
slashItems = Array.from(items);
|
|
}
|
|
|
|
function showSlashMenu(x, y) {
|
|
slashMenu.style.left = x + 'px';
|
|
slashMenu.style.top = y + 'px';
|
|
slashMenu.classList.add('visible');
|
|
slashVisible = true;
|
|
slashActiveIdx = -1;
|
|
slashSearch.value = '';
|
|
slashSearch.focus();
|
|
filterSlashItems('');
|
|
}
|
|
|
|
function hideSlashMenu() {
|
|
slashMenu.classList.remove('visible');
|
|
slashVisible = false;
|
|
resetSlashItems();
|
|
}
|
|
|
|
function filterSlashItems(query) {
|
|
var q = query.toLowerCase().trim();
|
|
slashItems.forEach(function(item, i) {
|
|
var title = (item.querySelector('.poc-slash-item-title') || {}).textContent || '';
|
|
var desc = (item.querySelector('.poc-slash-item-desc') || {}).textContent || '';
|
|
var match = !q || title.toLowerCase().indexOf(q) >= 0 || desc.toLowerCase().indexOf(q) >= 0;
|
|
item.style.display = match ? '' : 'none';
|
|
item.classList.remove('active');
|
|
});
|
|
// Hide section titles if all items in that section are hidden
|
|
slashMenu.querySelectorAll('.poc-slash-section-title').forEach(function(title) {
|
|
var next = title.nextElementSibling;
|
|
var allHidden = true;
|
|
while (next && !next.classList.contains('poc-slash-section-title') && !next.classList.contains('poc-slash-divider')) {
|
|
if (next.style.display !== 'none') { allHidden = false; break; }
|
|
next = next.nextElementSibling;
|
|
}
|
|
title.style.display = allHidden ? 'none' : '';
|
|
});
|
|
slashActiveIdx = -1;
|
|
}
|
|
|
|
function resetSlashItems() {
|
|
slashItems.forEach(function(item) {
|
|
item.style.display = '';
|
|
item.classList.remove('active');
|
|
});
|
|
slashMenu.querySelectorAll('.poc-slash-section-title').forEach(function(t) { t.style.display = ''; });
|
|
}
|
|
|
|
function getVisibleSlashItems() {
|
|
return slashItems.filter(function(item) { return item.style.display !== 'none'; });
|
|
}
|
|
|
|
function selectSlashAction(action) {
|
|
hideSlashMenu();
|
|
var msg = '';
|
|
switch(action) {
|
|
case 'text': msg = 'Plain text block'; break;
|
|
case 'h1': msg = 'Heading 1 inserted'; break;
|
|
case 'h2': msg = 'Heading 2 inserted'; break;
|
|
case 'bullet': msg = 'Bullet list inserted'; break;
|
|
case 'numbered': msg = 'Numbered list inserted'; break;
|
|
case 'quote': msg = 'Block quote inserted'; break;
|
|
case 'code': msg = 'Code block inserted'; break;
|
|
case 'image': msg = 'Image insertion dialog opened'; break;
|
|
case 'pdf': msg = 'PDF attachment dialog opened'; break;
|
|
case 'table': msg = 'Table inserted'; break;
|
|
case 'ai-continue': simAI('continue writing'); break;
|
|
case 'ai-improve': simAI('improve writing'); break;
|
|
case 'ai-summarize': simAI('summarize'); break;
|
|
case 'ai-translate': simAI('translate'); break;
|
|
default: msg = 'Action: ' + action;
|
|
}
|
|
if (msg) showToast(msg);
|
|
editor.focus();
|
|
}
|
|
|
|
// =========================================================================
|
|
// Zone 2b — Bubble Menu (on text selection)
|
|
// =========================================================================
|
|
|
|
function showBubbleMenu() {
|
|
var sel = window.getSelection();
|
|
if (!sel || sel.isCollapsed || !sel.rangeCount) {
|
|
hideBubbleMenu();
|
|
return;
|
|
}
|
|
|
|
// Check selection is inside editor
|
|
var range = sel.getRangeAt(0);
|
|
if (!editor.contains(range.commonAncestorContainer)) {
|
|
hideBubbleMenu();
|
|
return;
|
|
}
|
|
|
|
var rect = range.getBoundingClientRect();
|
|
if (!rect || rect.width === 0) { hideBubbleMenu(); return; }
|
|
|
|
var editorRect = editorArea.getBoundingClientRect();
|
|
var top = rect.top - editorRect.top - 48; // above selection
|
|
var left = rect.left - editorRect.left + (rect.width / 2) - (bubbleMenu.offsetWidth / 2);
|
|
|
|
// Keep in viewport
|
|
if (top < 8) top = rect.bottom - editorRect.top + 8; // below if too high
|
|
if (left < 8) left = 8;
|
|
if (left + bubbleMenu.offsetWidth > editorRect.width - 8) {
|
|
left = editorRect.width - bubbleMenu.offsetWidth - 8;
|
|
}
|
|
|
|
bubbleMenu.style.left = left + 'px';
|
|
bubbleMenu.style.top = top + 'px';
|
|
bubbleMenu.classList.add('visible');
|
|
bubbleVisible = true;
|
|
bubbleMoreMenu.classList.remove('visible');
|
|
}
|
|
|
|
function hideBubbleMenu() {
|
|
bubbleMenu.classList.remove('visible');
|
|
bubbleVisible = false;
|
|
bubbleMoreMenu.classList.remove('visible');
|
|
}
|
|
|
|
function execBubbleAction(action) {
|
|
switch(action) {
|
|
case 'bold': document.execCommand('bold'); break;
|
|
case 'italic': document.execCommand('italic'); break;
|
|
case 'strike': document.execCommand('strikeThrough'); break;
|
|
case 'code': wrapInline('code'); break;
|
|
case 'link': promptLink(); break;
|
|
case 'ai-improve': simAI('improve writing'); break;
|
|
case 'ai-grammar': simAI('fix grammar'); break;
|
|
case 'ai-translate': simAI('translate'); break;
|
|
case 'ai-continue': simAI('continue writing'); break;
|
|
case 'ai-summarize': simAI('summarize selection'); break;
|
|
case 'ai-tone': simAI('change tone'); break;
|
|
}
|
|
hideBubbleMenu();
|
|
editor.focus();
|
|
}
|
|
|
|
function wrapInline(tag) {
|
|
var sel = window.getSelection();
|
|
if (!sel || sel.isCollapsed) return;
|
|
var range = sel.getRangeAt(0);
|
|
var text = range.toString();
|
|
var node = document.createTextNode('<' + tag + '>' + text + '</' + tag + '>');
|
|
range.deleteContents();
|
|
range.insertNode(node);
|
|
}
|
|
|
|
function promptLink() {
|
|
var sel = window.getSelection();
|
|
if (!sel || sel.isCollapsed) return;
|
|
var url = prompt('Enter URL:');
|
|
if (url) {
|
|
document.execCommand('createLink', false, url);
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// Zone 3 — Quick Insert Buttons
|
|
// =========================================================================
|
|
|
|
function injectQuickInserts() {
|
|
var lines = editor.querySelectorAll('.line');
|
|
lines.forEach(function(line) {
|
|
if (line.querySelector('.poc-quick-insert')) return;
|
|
var btn = document.createElement('button');
|
|
btn.className = 'poc-quick-insert';
|
|
btn.textContent = '\u2295'; // ⊕
|
|
btn.title = 'Quick insert';
|
|
btn.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
var rect = btn.getBoundingClientRect();
|
|
showQuickInsertMenu(rect.left + 28, rect.top);
|
|
});
|
|
line.insertBefore(btn, line.firstChild);
|
|
});
|
|
}
|
|
|
|
function showQuickInsertMenu(x, y) {
|
|
var existing = document.getElementById('poc-quick-insert-menu');
|
|
if (existing) existing.remove();
|
|
|
|
var menu = document.createElement('div');
|
|
menu.id = 'poc-quick-insert-menu';
|
|
menu.style.cssText = [
|
|
'position:absolute;z-index:190;',
|
|
'left:' + x + 'px;top:' + y + 'px;',
|
|
'width:180px;background:var(--bg-elevated);',
|
|
'border:1px solid var(--border-light);border-radius:var(--radius);',
|
|
'box-shadow:var(--shadow-popover);padding:4px;',
|
|
'animation:pocFadeSlideIn 100ms ease'
|
|
].join('');
|
|
|
|
var actions = [
|
|
{icon:'\uD83D\uDDBC', label:'Image', action:'image'},
|
|
{icon:'\uD83D\uDCC4', label:'PDF', action:'pdf'},
|
|
{icon:'</>', label:'Code Block', action:'code'},
|
|
{icon:'\uD83D\uDCCA', label:'Table', action:'table'},
|
|
{icon:'\u2728', label:'AI Actions', action:'ai'}
|
|
];
|
|
|
|
actions.forEach(function(a) {
|
|
var item = document.createElement('div');
|
|
item.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 10px;font-size:13px;color:var(--text-secondary);cursor:pointer;border-radius:var(--radius-sm);';
|
|
item.innerHTML = '<span style="font-size:15px">' + a.icon + '</span><span>' + a.label + '</span>';
|
|
item.addEventListener('mouseenter', function() { item.style.background = 'var(--bg-hover)'; });
|
|
item.addEventListener('mouseleave', function() { item.style.background = ''; });
|
|
item.addEventListener('click', function() {
|
|
menu.remove();
|
|
if (a.action === 'ai') {
|
|
showToast('AI actions opened');
|
|
} else {
|
|
selectSlashAction(a.action);
|
|
}
|
|
});
|
|
menu.appendChild(item);
|
|
});
|
|
|
|
editorArea.appendChild(menu);
|
|
|
|
// Click outside closes
|
|
setTimeout(function() {
|
|
document.addEventListener('click', function closeMenu() {
|
|
document.removeEventListener('click', closeMenu);
|
|
if (menu.parentNode) menu.remove();
|
|
});
|
|
}, 10);
|
|
}
|
|
|
|
|
|
// =========================================================================
|
|
// Zone 4 — AI Side Panel
|
|
// =========================================================================
|
|
|
|
function toggleAIPanel() {
|
|
var hidden = aiPanel.classList.toggle('hidden');
|
|
btnAI.classList.toggle('active', !hidden);
|
|
if (!hidden) {
|
|
document.getElementById('poc-ai-input').focus();
|
|
}
|
|
}
|
|
|
|
function sendAIMessage() {
|
|
var input = document.getElementById('poc-ai-input');
|
|
var chat = document.getElementById('poc-ai-chat');
|
|
var text = input.value.trim();
|
|
if (!text) return;
|
|
|
|
var userMsg = document.createElement('div');
|
|
userMsg.style.cssText = 'background:var(--bg-tertiary);padding:8px 12px;border-radius:var(--radius-sm);font-size:13px;align-self:flex-end;max-width:80%;';
|
|
userMsg.textContent = text;
|
|
chat.appendChild(userMsg);
|
|
|
|
input.value = '';
|
|
showAIProcessing();
|
|
|
|
setTimeout(function() {
|
|
var aiMsg = document.createElement('div');
|
|
aiMsg.style.cssText = 'background:var(--accent-ai-bg);padding:8px 12px;border-radius:var(--radius-sm);font-size:13px;color:var(--accent-ai);max-width:80%;';
|
|
aiMsg.textContent = 'AI response to: "' + text + '" (simulated)';
|
|
chat.appendChild(aiMsg);
|
|
chat.scrollTop = chat.scrollHeight;
|
|
hideAIProcessing();
|
|
}, 800);
|
|
}
|
|
|
|
function showAIProcessing() {
|
|
aiStatus.classList.add('visible');
|
|
}
|
|
|
|
function hideAIProcessing() {
|
|
aiStatus.classList.remove('visible');
|
|
}
|
|
|
|
function simAI(action) {
|
|
showAIProcessing();
|
|
showToast('AI: ' + action + ' (simulated)');
|
|
setTimeout(hideAIProcessing, 1200);
|
|
}
|
|
|
|
// =========================================================================
|
|
// Zone 5 — Drag & Drop
|
|
// =========================================================================
|
|
|
|
var dragCounter = 0;
|
|
|
|
document.addEventListener('dragenter', function(e) {
|
|
e.preventDefault();
|
|
dragCounter++;
|
|
if (dragCounter === 1) dropOverlay.classList.add('visible');
|
|
});
|
|
|
|
document.addEventListener('dragleave', function(e) {
|
|
e.preventDefault();
|
|
dragCounter--;
|
|
if (dragCounter === 0) dropOverlay.classList.remove('visible');
|
|
});
|
|
|
|
document.addEventListener('dragover', function(e) {
|
|
e.preventDefault();
|
|
});
|
|
|
|
document.addEventListener('drop', function(e) {
|
|
e.preventDefault();
|
|
dragCounter = 0;
|
|
dropOverlay.classList.remove('visible');
|
|
|
|
var files = e.dataTransfer.files;
|
|
if (files.length > 0) {
|
|
var names = Array.from(files).map(function(f) { return f.name; }).join(', ');
|
|
showToast('File(s) dropped: ' + names + ' (upload simulated)');
|
|
}
|
|
});
|
|
|
|
// =========================================================================
|
|
// Event Handlers
|
|
// =========================================================================
|
|
|
|
// Slash menu trigger: type '/' at start of line or on empty line
|
|
editor.addEventListener('keydown', function(e) {
|
|
if (e.key === '/') {
|
|
// Check if we're in a position where slash makes sense (start of line or after space)
|
|
var sel = window.getSelection();
|
|
if (sel && sel.rangeCount) {
|
|
var range = sel.getRangeAt(0);
|
|
var node = range.startContainer;
|
|
var offset = range.startOffset;
|
|
|
|
// Only trigger on empty or near-empty context
|
|
var text = (node.textContent || '').substring(0, offset).trim();
|
|
if (text === '' || text === '') {
|
|
// Allow '/' to be typed, but also show menu
|
|
setTimeout(function() {
|
|
// Check if a '/' was actually inserted
|
|
var caret = getCaretCoordinates();
|
|
if (caret) {
|
|
showSlashMenu(caret.x, caret.y + 20);
|
|
}
|
|
}, 50);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (slashVisible) {
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
hideSlashMenu();
|
|
} else if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
navigateSlash(1);
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
navigateSlash(-1);
|
|
} else if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
var visible = getVisibleSlashItems();
|
|
if (slashActiveIdx >= 0 && slashActiveIdx < visible.length) {
|
|
var action = visible[slashActiveIdx].getAttribute('data-action');
|
|
selectSlashAction(action);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ctrl+J for AI panel toggle
|
|
if (e.ctrlKey && e.key === 'j') {
|
|
e.preventDefault();
|
|
toggleAIPanel();
|
|
}
|
|
});
|
|
|
|
slashSearch.addEventListener('input', function() {
|
|
filterSlashItems(slashSearch.value);
|
|
});
|
|
|
|
slashMenu.addEventListener('click', function(e) {
|
|
var item = e.target.closest('.poc-slash-item');
|
|
if (item) {
|
|
var action = item.getAttribute('data-action');
|
|
selectSlashAction(action);
|
|
}
|
|
});
|
|
|
|
function navigateSlash(dir) {
|
|
var visible = getVisibleSlashItems();
|
|
if (visible.length === 0) return;
|
|
slashActiveIdx += dir;
|
|
if (slashActiveIdx < 0) slashActiveIdx = visible.length - 1;
|
|
if (slashActiveIdx >= visible.length) slashActiveIdx = 0;
|
|
visible.forEach(function(item, i) {
|
|
item.classList.toggle('active', i === slashActiveIdx);
|
|
});
|
|
}
|
|
|
|
// Bubble menu on selection change
|
|
document.addEventListener('selectionchange', function() {
|
|
clearTimeout(window._bubbleTimeout);
|
|
window._bubbleTimeout = setTimeout(function() {
|
|
var sel = window.getSelection();
|
|
if (sel && !sel.isCollapsed && editor.contains(sel.anchorNode)) {
|
|
showBubbleMenu();
|
|
} else if (!bubbleMoreMenu.classList.contains('visible')) {
|
|
hideBubbleMenu();
|
|
}
|
|
}, 80);
|
|
});
|
|
|
|
// Bubble menu click handlers
|
|
bubbleMenu.addEventListener('click', function(e) {
|
|
var btn = e.target.closest('.poc-bubble-btn');
|
|
if (!btn) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (btn.id === 'poc-bubble-more') {
|
|
bubbleMoreMenu.classList.toggle('visible');
|
|
return;
|
|
}
|
|
if (btn.id === 'poc-bubble-ai') {
|
|
bubbleMoreMenu.classList.toggle('visible');
|
|
return;
|
|
}
|
|
var action = btn.getAttribute('data-action');
|
|
if (action) execBubbleAction(action);
|
|
});
|
|
|
|
bubbleMoreMenu.addEventListener('click', function(e) {
|
|
var item = e.target.closest('.poc-bubble-more-item');
|
|
if (!item) return;
|
|
e.stopPropagation();
|
|
var action = item.getAttribute('data-action');
|
|
execBubbleAction(action);
|
|
});
|
|
|
|
// Click outside closes menus
|
|
document.addEventListener('click', function(e) {
|
|
if (slashVisible && !slashMenu.contains(e.target) && e.target !== editor) {
|
|
hideSlashMenu();
|
|
}
|
|
if (bubbleVisible && !bubbleMenu.contains(e.target)) {
|
|
hideBubbleMenu();
|
|
}
|
|
var qiMenu = document.getElementById('poc-quick-insert-menu');
|
|
if (qiMenu && !qiMenu.contains(e.target)) {
|
|
qiMenu.remove();
|
|
}
|
|
});
|
|
|
|
// AI Panel buttons
|
|
btnAI.addEventListener('click', toggleAIPanel);
|
|
document.getElementById('poc-ai-panel-close').addEventListener('click', toggleAIPanel);
|
|
document.getElementById('poc-ai-send').addEventListener('click', sendAIMessage);
|
|
document.getElementById('poc-ai-input').addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter') sendAIMessage();
|
|
});
|
|
|
|
// AI suggestions
|
|
document.querySelectorAll('.poc-ai-suggestion').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
var text = btn.textContent.trim();
|
|
document.getElementById('poc-ai-input').value = text;
|
|
sendAIMessage();
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// Save indicator & auto-save simulation
|
|
// =========================================================================
|
|
|
|
editor.addEventListener('input', function() {
|
|
updateWordCount();
|
|
clearTimeout(saveTimer);
|
|
saveIndicator.innerHTML = '<span style="color:var(--warning);font-size:12px">Saving...</span>';
|
|
saveTimer = setTimeout(function() {
|
|
saveIndicator.innerHTML = '<span class="poc-save-dot"></span> Saved';
|
|
}, 800);
|
|
});
|
|
|
|
function updateWordCount() {
|
|
var text = editor.textContent || '';
|
|
var words = text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
wordCount.textContent = words + ' words';
|
|
}
|
|
|
|
// Title auto-save
|
|
document.getElementById('poc-title').addEventListener('input', function() {
|
|
clearTimeout(saveTimer);
|
|
saveIndicator.innerHTML = '<span style="color:var(--warning);font-size:12px">Saving...</span>';
|
|
saveTimer = setTimeout(function() {
|
|
saveIndicator.innerHTML = '<span class="poc-save-dot"></span> Saved';
|
|
}, 800);
|
|
});
|
|
|
|
// =========================================================================
|
|
// Toast
|
|
// =========================================================================
|
|
|
|
var toastTimer = null;
|
|
function showToast(msg) {
|
|
toast.textContent = msg;
|
|
toast.classList.add('visible');
|
|
clearTimeout(toastTimer);
|
|
toastTimer = setTimeout(function() {
|
|
toast.classList.remove('visible');
|
|
}, 2000);
|
|
}
|
|
|
|
// =========================================================================
|
|
// Caret coordinate helper
|
|
// =========================================================================
|
|
|
|
function getCaretCoordinates() {
|
|
var sel = window.getSelection();
|
|
if (!sel || !sel.rangeCount) return null;
|
|
var range = sel.getRangeAt(0);
|
|
var rect = range.getClientRects()[0];
|
|
if (!rect) return null;
|
|
var editorRect = editorArea.getBoundingClientRect();
|
|
return {
|
|
x: rect.left - editorRect.left,
|
|
y: rect.top - editorRect.top
|
|
};
|
|
}
|
|
|
|
// =========================================================================
|
|
// Init
|
|
// =========================================================================
|
|
|
|
initSlashItems();
|
|
injectQuickInserts();
|
|
updateWordCount();
|
|
|
|
console.log('ObsiGate Editor POC initialized — all 5 zones active');
|
|
console.log(' Zone 1: Top Fixed Bar (always visible)');
|
|
console.log(' Zone 2a: Slash Command Menu (type / on empty line)');
|
|
console.log(' Zone 2b: Bubble Menu (select text)');
|
|
console.log(' Zone 3: Quick Insert (hover left of lines)');
|
|
console.log(' Zone 4: AI Side Panel (Ctrl+J or AI button)');
|
|
console.log(' Zone 5: Drag & Drop (drag any file over)');
|
|
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html> |