feat(suite): Enhanced UI for Sheet, Docs, and Slides editors
This commit is contained in:
parent
10299814b2
commit
76627ae9f0
9 changed files with 6475 additions and 27 deletions
|
|
@ -1098,6 +1098,377 @@
|
||||||
background: rgba(66, 133, 244, 0.3);
|
background: rgba(66, 133, 244, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
FIND & REPLACE MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.find-replace-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-replace-group label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-replace-group input {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-replace-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-replace-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-results {
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-highlight {
|
||||||
|
background: #ffeb3b;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-highlight.current {
|
||||||
|
background: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
PRINT PREVIEW MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.modal-fullscreen {
|
||||||
|
width: 95vw;
|
||||||
|
max-width: 1200px;
|
||||||
|
height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-fullscreen .modal-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-fullscreen .modal-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-toolbar select {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 13px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-toolbar .checkbox-label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-preview-body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
background: var(--sentient-bg-tertiary, #e0e0e0);
|
||||||
|
overflow: auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-preview-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-page {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
padding: 48px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-page.portrait {
|
||||||
|
width: 8.5in;
|
||||||
|
min-height: 11in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-page.landscape {
|
||||||
|
width: 11in;
|
||||||
|
min-height: 8.5in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-header,
|
||||||
|
.print-footer {
|
||||||
|
font-size: 10pt;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-header {
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-footer {
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-content {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 12pt;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
PAGE BREAK
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.page-break {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
margin: 24px 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent 0%,
|
||||||
|
var(--sentient-border, #e0e0e0) 10%,
|
||||||
|
var(--sentient-border, #e0e0e0) 90%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-break::before {
|
||||||
|
content: "Page Break";
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
padding: 2px 12px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--sentient-text-muted, #999);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.page-break {
|
||||||
|
display: block;
|
||||||
|
page-break-after: always;
|
||||||
|
height: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-break::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
HEADER & FOOTER
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.editor-header,
|
||||||
|
.editor-footer {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
border: 1px dashed transparent;
|
||||||
|
transition: border-color 0.15s ease, background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header {
|
||||||
|
border-bottom: 1px dashed var(--sentient-border, #e0e0e0);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-footer {
|
||||||
|
border-top: 1px dashed var(--sentient-border, #e0e0e0);
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header:hover,
|
||||||
|
.editor-footer:hover {
|
||||||
|
background: rgba(66, 133, 244, 0.02);
|
||||||
|
border-color: var(--sentient-border, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header:focus,
|
||||||
|
.editor-footer:focus {
|
||||||
|
outline: none;
|
||||||
|
background: rgba(66, 133, 244, 0.05);
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header:empty::before,
|
||||||
|
.editor-footer:empty::before {
|
||||||
|
content: attr(data-placeholder);
|
||||||
|
color: var(--sentient-text-muted, #999);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header:focus:empty::before,
|
||||||
|
.editor-footer:focus:empty::before {
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header/Footer Modal */
|
||||||
|
.hf-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-tab {
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-tab:hover {
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-tab.active {
|
||||||
|
color: var(--sentient-accent, #4285f4);
|
||||||
|
border-bottom-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-editor {
|
||||||
|
min-height: 80px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-editor:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-editor:empty::before {
|
||||||
|
content: attr(data-placeholder);
|
||||||
|
color: var(--sentient-text-muted, #999);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-insert-options {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-insert-options label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hf-insert-btns {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.editor-header,
|
||||||
|
.editor-footer {
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header:empty,
|
||||||
|
.editor-footer:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.editor-content ::-moz-selection {
|
.editor-content ::-moz-selection {
|
||||||
background: rgba(66, 133, 244, 0.3);
|
background: rgba(66, 133, 244, 0.3);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,46 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-center">
|
<div class="toolbar-center">
|
||||||
|
<div class="toolbar-group">
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="printPreviewBtn"
|
||||||
|
title="Print Preview"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||||
|
<path
|
||||||
|
d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
|
||||||
|
></path>
|
||||||
|
<rect x="6" y="14" width="12" height="8"></rect>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="findReplaceBtn"
|
||||||
|
title="Find & Replace (Ctrl+H)"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="toolbar-divider"></span>
|
||||||
<div class="toolbar-group">
|
<div class="toolbar-group">
|
||||||
<button class="btn-icon" id="undoBtn" title="Undo (Ctrl+Z)">
|
<button class="btn-icon" id="undoBtn" title="Undo (Ctrl+Z)">
|
||||||
<svg
|
<svg
|
||||||
|
|
@ -316,6 +356,40 @@
|
||||||
</div>
|
</div>
|
||||||
<span class="toolbar-divider"></span>
|
<span class="toolbar-divider"></span>
|
||||||
<div class="toolbar-group">
|
<div class="toolbar-group">
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="headerFooterBtn"
|
||||||
|
title="Edit Header & Footer"
|
||||||
|
>
|
||||||
|
<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"></rect>
|
||||||
|
<line x1="3" y1="7" x2="21" y2="7"></line>
|
||||||
|
<line x1="3" y1="17" x2="21" y2="17"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="pageBreakBtn"
|
||||||
|
title="Insert Page Break"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M3 12h4l3-9 4 18 3-9h4"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button class="btn-icon" id="linkBtn" title="Insert Link">
|
<button class="btn-icon" id="linkBtn" title="Insert Link">
|
||||||
<svg
|
<svg
|
||||||
width="16"
|
width="16"
|
||||||
|
|
@ -415,6 +489,12 @@
|
||||||
<div class="docs-main">
|
<div class="docs-main">
|
||||||
<div class="docs-canvas">
|
<div class="docs-canvas">
|
||||||
<div class="editor-page" id="editorPage">
|
<div class="editor-page" id="editorPage">
|
||||||
|
<div
|
||||||
|
class="editor-header"
|
||||||
|
id="editorHeader"
|
||||||
|
contenteditable="true"
|
||||||
|
data-placeholder="Click to add header"
|
||||||
|
></div>
|
||||||
<div
|
<div
|
||||||
class="editor-content"
|
class="editor-content"
|
||||||
id="editorContent"
|
id="editorContent"
|
||||||
|
|
@ -422,6 +502,12 @@
|
||||||
spellcheck="true"
|
spellcheck="true"
|
||||||
data-placeholder="Start typing..."
|
data-placeholder="Start typing..."
|
||||||
></div>
|
></div>
|
||||||
|
<div
|
||||||
|
class="editor-footer"
|
||||||
|
id="editorFooter"
|
||||||
|
contenteditable="true"
|
||||||
|
data-placeholder="Click to add footer"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -731,4 +817,214 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="findReplaceModal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Find & Replace</h3>
|
||||||
|
<button class="btn-close" id="closeFindReplaceModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="find-replace-group">
|
||||||
|
<label>Find:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="findInput"
|
||||||
|
placeholder="Search text..."
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="find-replace-group">
|
||||||
|
<label>Replace:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="replaceInput"
|
||||||
|
placeholder="Replace with..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="find-replace-options">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="findMatchCase" />
|
||||||
|
Match case
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="findWholeWord" />
|
||||||
|
Whole words only
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="find-results" id="findResults">
|
||||||
|
<span>0 matches found</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="findPrevBtn">Previous</button>
|
||||||
|
<button class="btn-secondary" id="findNextBtn">Next</button>
|
||||||
|
<button class="btn-primary" id="replaceBtn">Replace</button>
|
||||||
|
<button class="btn-primary" id="replaceAllBtn">
|
||||||
|
Replace All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="printPreviewModal">
|
||||||
|
<div class="modal-content modal-fullscreen">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Print Preview</h3>
|
||||||
|
<div class="print-toolbar">
|
||||||
|
<select id="printOrientation">
|
||||||
|
<option value="portrait">Portrait</option>
|
||||||
|
<option value="landscape">Landscape</option>
|
||||||
|
</select>
|
||||||
|
<select id="printPaperSize">
|
||||||
|
<option value="letter">Letter (8.5" x 11")</option>
|
||||||
|
<option value="a4">A4 (210mm x 297mm)</option>
|
||||||
|
<option value="legal">Legal (8.5" x 14")</option>
|
||||||
|
</select>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="printHeaders" />
|
||||||
|
Headers & Footers
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn-close" id="closePrintPreviewModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body print-preview-body">
|
||||||
|
<div class="print-preview-container" id="printPreviewContainer">
|
||||||
|
<div class="print-page" id="printPage">
|
||||||
|
<div class="print-header" id="printHeader"></div>
|
||||||
|
<div class="print-content" id="printContent"></div>
|
||||||
|
<div class="print-footer" id="printFooter"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="cancelPrintBtn">Cancel</button>
|
||||||
|
<button class="btn-primary" id="printBtn">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||||
|
<path
|
||||||
|
d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
|
||||||
|
></path>
|
||||||
|
<rect x="6" y="14" width="12" height="8"></rect>
|
||||||
|
</svg>
|
||||||
|
Print
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="headerFooterModal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Header & Footer</h3>
|
||||||
|
<button class="btn-close" id="closeHeaderFooterModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="hf-tabs">
|
||||||
|
<button class="hf-tab active" data-tab="header">Header</button>
|
||||||
|
<button class="hf-tab" data-tab="footer">Footer</button>
|
||||||
|
</div>
|
||||||
|
<div class="hf-tab-content active" id="hfHeaderTab">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Header Content:</label>
|
||||||
|
<div
|
||||||
|
class="hf-editor"
|
||||||
|
id="headerEditor"
|
||||||
|
contenteditable="true"
|
||||||
|
data-placeholder="Enter header text..."
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="hf-options">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="showHeaderFirstPage" />
|
||||||
|
Different first page
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="showHeaderOddEven" />
|
||||||
|
Different odd & even pages
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="hf-insert-options">
|
||||||
|
<label>Insert:</label>
|
||||||
|
<div class="hf-insert-btns">
|
||||||
|
<button class="btn-secondary btn-sm" id="insertPageNum">
|
||||||
|
Page Number
|
||||||
|
</button>
|
||||||
|
<button class="btn-secondary btn-sm" id="insertDate">
|
||||||
|
Date
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-secondary btn-sm"
|
||||||
|
id="insertDocTitle"
|
||||||
|
>
|
||||||
|
Document Title
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hf-tab-content" id="hfFooterTab">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Footer Content:</label>
|
||||||
|
<div
|
||||||
|
class="hf-editor"
|
||||||
|
id="footerEditor"
|
||||||
|
contenteditable="true"
|
||||||
|
data-placeholder="Enter footer text..."
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="hf-options">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="showFooterFirstPage" />
|
||||||
|
Different first page
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="showFooterOddEven" />
|
||||||
|
Different odd & even pages
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="hf-insert-options">
|
||||||
|
<label>Insert:</label>
|
||||||
|
<div class="hf-insert-btns">
|
||||||
|
<button
|
||||||
|
class="btn-secondary btn-sm"
|
||||||
|
id="insertFooterPageNum"
|
||||||
|
>
|
||||||
|
Page Number
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-secondary btn-sm"
|
||||||
|
id="insertFooterDate"
|
||||||
|
>
|
||||||
|
Date
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-secondary btn-sm"
|
||||||
|
id="insertFooterDocTitle"
|
||||||
|
>
|
||||||
|
Document Title
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="removeHeaderFooterBtn">
|
||||||
|
Remove All
|
||||||
|
</button>
|
||||||
|
<button class="btn-secondary" id="cancelHeaderFooterBtn">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button class="btn-primary" id="applyHeaderFooterBtn">
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="docs/docs.js"></script>
|
<script src="docs/docs.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@
|
||||||
chatPanelOpen: true,
|
chatPanelOpen: true,
|
||||||
driveSource: null,
|
driveSource: null,
|
||||||
zoom: 100,
|
zoom: 100,
|
||||||
|
findMatches: [],
|
||||||
|
findMatchIndex: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const elements = {};
|
const elements = {};
|
||||||
|
|
@ -54,6 +56,11 @@
|
||||||
elements.imageModal = document.getElementById("imageModal");
|
elements.imageModal = document.getElementById("imageModal");
|
||||||
elements.tableModal = document.getElementById("tableModal");
|
elements.tableModal = document.getElementById("tableModal");
|
||||||
elements.exportModal = document.getElementById("exportModal");
|
elements.exportModal = document.getElementById("exportModal");
|
||||||
|
elements.findReplaceModal = document.getElementById("findReplaceModal");
|
||||||
|
elements.printPreviewModal = document.getElementById("printPreviewModal");
|
||||||
|
elements.headerFooterModal = document.getElementById("headerFooterModal");
|
||||||
|
elements.editorHeader = document.getElementById("editorHeader");
|
||||||
|
elements.editorFooter = document.getElementById("editorFooter");
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
|
|
@ -207,6 +214,94 @@
|
||||||
btn.addEventListener("click", () => exportDocument(btn.dataset.format));
|
btn.addEventListener("click", () => exportDocument(btn.dataset.format));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("findReplaceBtn")
|
||||||
|
?.addEventListener("click", showFindReplaceModal);
|
||||||
|
document
|
||||||
|
.getElementById("closeFindReplaceModal")
|
||||||
|
?.addEventListener("click", () => hideModal("findReplaceModal"));
|
||||||
|
document.getElementById("findNextBtn")?.addEventListener("click", findNext);
|
||||||
|
document.getElementById("findPrevBtn")?.addEventListener("click", findPrev);
|
||||||
|
document
|
||||||
|
.getElementById("replaceBtn")
|
||||||
|
?.addEventListener("click", replaceOne);
|
||||||
|
document
|
||||||
|
.getElementById("replaceAllBtn")
|
||||||
|
?.addEventListener("click", replaceAll);
|
||||||
|
document
|
||||||
|
.getElementById("findInput")
|
||||||
|
?.addEventListener("input", performFind);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("printPreviewBtn")
|
||||||
|
?.addEventListener("click", showPrintPreview);
|
||||||
|
document
|
||||||
|
.getElementById("closePrintPreviewModal")
|
||||||
|
?.addEventListener("click", () => hideModal("printPreviewModal"));
|
||||||
|
document
|
||||||
|
.getElementById("printBtn")
|
||||||
|
?.addEventListener("click", printDocument);
|
||||||
|
document
|
||||||
|
.getElementById("cancelPrintBtn")
|
||||||
|
?.addEventListener("click", () => hideModal("printPreviewModal"));
|
||||||
|
document
|
||||||
|
.getElementById("printOrientation")
|
||||||
|
?.addEventListener("change", updatePrintPreview);
|
||||||
|
document
|
||||||
|
.getElementById("printPaperSize")
|
||||||
|
?.addEventListener("change", updatePrintPreview);
|
||||||
|
document
|
||||||
|
.getElementById("printHeaders")
|
||||||
|
?.addEventListener("change", updatePrintPreview);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("pageBreakBtn")
|
||||||
|
?.addEventListener("click", insertPageBreak);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("headerFooterBtn")
|
||||||
|
?.addEventListener("click", showHeaderFooterModal);
|
||||||
|
document
|
||||||
|
.getElementById("closeHeaderFooterModal")
|
||||||
|
?.addEventListener("click", () => hideModal("headerFooterModal"));
|
||||||
|
document
|
||||||
|
.getElementById("applyHeaderFooterBtn")
|
||||||
|
?.addEventListener("click", applyHeaderFooter);
|
||||||
|
document
|
||||||
|
.getElementById("cancelHeaderFooterBtn")
|
||||||
|
?.addEventListener("click", () => hideModal("headerFooterModal"));
|
||||||
|
document
|
||||||
|
.getElementById("removeHeaderFooterBtn")
|
||||||
|
?.addEventListener("click", removeHeaderFooter);
|
||||||
|
document.querySelectorAll(".hf-tab").forEach((tab) => {
|
||||||
|
tab.addEventListener("click", () => switchHfTab(tab.dataset.tab));
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById("insertPageNum")
|
||||||
|
?.addEventListener("click", () => insertHfField("header", "pageNum"));
|
||||||
|
document
|
||||||
|
.getElementById("insertDate")
|
||||||
|
?.addEventListener("click", () => insertHfField("header", "date"));
|
||||||
|
document
|
||||||
|
.getElementById("insertDocTitle")
|
||||||
|
?.addEventListener("click", () => insertHfField("header", "title"));
|
||||||
|
document
|
||||||
|
.getElementById("insertFooterPageNum")
|
||||||
|
?.addEventListener("click", () => insertHfField("footer", "pageNum"));
|
||||||
|
document
|
||||||
|
.getElementById("insertFooterDate")
|
||||||
|
?.addEventListener("click", () => insertHfField("footer", "date"));
|
||||||
|
document
|
||||||
|
.getElementById("insertFooterDocTitle")
|
||||||
|
?.addEventListener("click", () => insertHfField("footer", "title"));
|
||||||
|
|
||||||
|
if (elements.editorHeader) {
|
||||||
|
elements.editorHeader.addEventListener("input", handleHeaderFooterInput);
|
||||||
|
}
|
||||||
|
if (elements.editorFooter) {
|
||||||
|
elements.editorFooter.addEventListener("input", handleHeaderFooterInput);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1007,6 +1102,421 @@ ${content}
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showFindReplaceModal() {
|
||||||
|
showModal("findReplaceModal");
|
||||||
|
document.getElementById("findInput")?.focus();
|
||||||
|
state.findMatches = [];
|
||||||
|
state.findMatchIndex = -1;
|
||||||
|
clearFindHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
|
function performFind() {
|
||||||
|
const searchText = document.getElementById("findInput")?.value || "";
|
||||||
|
const matchCase = document.getElementById("findMatchCase")?.checked;
|
||||||
|
const wholeWord = document.getElementById("findWholeWord")?.checked;
|
||||||
|
|
||||||
|
clearFindHighlights();
|
||||||
|
state.findMatches = [];
|
||||||
|
state.findMatchIndex = -1;
|
||||||
|
|
||||||
|
if (!searchText || !elements.editorContent) {
|
||||||
|
updateFindResults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = elements.editorContent.innerHTML;
|
||||||
|
let flags = "g";
|
||||||
|
if (!matchCase) flags += "i";
|
||||||
|
|
||||||
|
let searchPattern = searchText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
if (wholeWord) {
|
||||||
|
searchPattern = `\\b${searchPattern}\\b`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(searchPattern, flags);
|
||||||
|
const textContent = elements.editorContent.textContent;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = regex.exec(textContent)) !== null) {
|
||||||
|
state.findMatches.push({
|
||||||
|
index: match.index,
|
||||||
|
length: match[0].length,
|
||||||
|
text: match[0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.findMatches.length > 0) {
|
||||||
|
state.findMatchIndex = 0;
|
||||||
|
highlightAllMatches(searchText, matchCase, wholeWord);
|
||||||
|
scrollToMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFindResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightAllMatches(searchText, matchCase, wholeWord) {
|
||||||
|
if (!elements.editorContent) return;
|
||||||
|
|
||||||
|
const walker = document.createTreeWalker(
|
||||||
|
elements.editorContent,
|
||||||
|
NodeFilter.SHOW_TEXT,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
const textNodes = [];
|
||||||
|
let node;
|
||||||
|
while ((node = walker.nextNode())) {
|
||||||
|
textNodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
let flags = "g";
|
||||||
|
if (!matchCase) flags += "i";
|
||||||
|
let searchPattern = searchText.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
if (wholeWord) {
|
||||||
|
searchPattern = `\\b${searchPattern}\\b`;
|
||||||
|
}
|
||||||
|
const regex = new RegExp(`(${searchPattern})`, flags);
|
||||||
|
|
||||||
|
textNodes.forEach((textNode) => {
|
||||||
|
const text = textNode.textContent;
|
||||||
|
if (regex.test(text)) {
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.innerHTML = text.replace(
|
||||||
|
regex,
|
||||||
|
'<mark class="find-highlight">$1</mark>',
|
||||||
|
);
|
||||||
|
textNode.parentNode.replaceChild(span, textNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateCurrentHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCurrentHighlight() {
|
||||||
|
const highlights =
|
||||||
|
elements.editorContent?.querySelectorAll(".find-highlight");
|
||||||
|
if (!highlights) return;
|
||||||
|
|
||||||
|
highlights.forEach((el, index) => {
|
||||||
|
el.classList.toggle("current", index === state.findMatchIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFindHighlights() {
|
||||||
|
if (!elements.editorContent) return;
|
||||||
|
|
||||||
|
const highlights =
|
||||||
|
elements.editorContent.querySelectorAll(".find-highlight");
|
||||||
|
highlights.forEach((el) => {
|
||||||
|
const parent = el.parentNode;
|
||||||
|
parent.replaceChild(document.createTextNode(el.textContent), el);
|
||||||
|
parent.normalize();
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapperSpans = elements.editorContent.querySelectorAll("span:empty");
|
||||||
|
wrapperSpans.forEach((span) => {
|
||||||
|
if (span.childNodes.length === 0) {
|
||||||
|
span.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFindResults() {
|
||||||
|
const resultsEl = document.getElementById("findResults");
|
||||||
|
if (resultsEl) {
|
||||||
|
const count = state.findMatches.length;
|
||||||
|
const span = resultsEl.querySelector("span");
|
||||||
|
if (span) {
|
||||||
|
span.textContent =
|
||||||
|
count === 0
|
||||||
|
? "0 matches found"
|
||||||
|
: `${state.findMatchIndex + 1} of ${count} matches`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToMatch() {
|
||||||
|
const highlights =
|
||||||
|
elements.editorContent?.querySelectorAll(".find-highlight");
|
||||||
|
if (highlights && highlights[state.findMatchIndex]) {
|
||||||
|
highlights[state.findMatchIndex].scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNext() {
|
||||||
|
if (state.findMatches.length === 0) return;
|
||||||
|
state.findMatchIndex =
|
||||||
|
(state.findMatchIndex + 1) % state.findMatches.length;
|
||||||
|
updateCurrentHighlight();
|
||||||
|
scrollToMatch();
|
||||||
|
updateFindResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPrev() {
|
||||||
|
if (state.findMatches.length === 0) return;
|
||||||
|
state.findMatchIndex =
|
||||||
|
(state.findMatchIndex - 1 + state.findMatches.length) %
|
||||||
|
state.findMatches.length;
|
||||||
|
updateCurrentHighlight();
|
||||||
|
scrollToMatch();
|
||||||
|
updateFindResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceOne() {
|
||||||
|
if (state.findMatches.length === 0 || state.findMatchIndex < 0) return;
|
||||||
|
|
||||||
|
const replaceText = document.getElementById("replaceInput")?.value || "";
|
||||||
|
const highlights =
|
||||||
|
elements.editorContent?.querySelectorAll(".find-highlight");
|
||||||
|
|
||||||
|
if (highlights && highlights[state.findMatchIndex]) {
|
||||||
|
const highlight = highlights[state.findMatchIndex];
|
||||||
|
highlight.replaceWith(document.createTextNode(replaceText));
|
||||||
|
elements.editorContent.normalize();
|
||||||
|
|
||||||
|
state.findMatches.splice(state.findMatchIndex, 1);
|
||||||
|
if (state.findMatches.length > 0) {
|
||||||
|
state.findMatchIndex = state.findMatchIndex % state.findMatches.length;
|
||||||
|
updateCurrentHighlight();
|
||||||
|
scrollToMatch();
|
||||||
|
} else {
|
||||||
|
state.findMatchIndex = -1;
|
||||||
|
}
|
||||||
|
updateFindResults();
|
||||||
|
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceAll() {
|
||||||
|
if (state.findMatches.length === 0) return;
|
||||||
|
|
||||||
|
const replaceText = document.getElementById("replaceInput")?.value || "";
|
||||||
|
const highlights =
|
||||||
|
elements.editorContent?.querySelectorAll(".find-highlight");
|
||||||
|
|
||||||
|
if (highlights) {
|
||||||
|
const count = highlights.length;
|
||||||
|
highlights.forEach((highlight) => {
|
||||||
|
highlight.replaceWith(document.createTextNode(replaceText));
|
||||||
|
});
|
||||||
|
elements.editorContent.normalize();
|
||||||
|
|
||||||
|
state.findMatches = [];
|
||||||
|
state.findMatchIndex = -1;
|
||||||
|
updateFindResults();
|
||||||
|
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
addChatMessage("assistant", `Replaced ${count} occurrences.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPrintPreview() {
|
||||||
|
showModal("printPreviewModal");
|
||||||
|
updatePrintPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePrintPreview() {
|
||||||
|
const orientation =
|
||||||
|
document.getElementById("printOrientation")?.value || "portrait";
|
||||||
|
const showHeaders = document.getElementById("printHeaders")?.checked;
|
||||||
|
const printPage = document.getElementById("printPage");
|
||||||
|
const printContent = document.getElementById("printContent");
|
||||||
|
const printHeader = document.getElementById("printHeader");
|
||||||
|
const printFooter = document.getElementById("printFooter");
|
||||||
|
|
||||||
|
if (printPage) {
|
||||||
|
printPage.className = `print-page ${orientation}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printHeader) {
|
||||||
|
printHeader.innerHTML = showHeaders ? state.docTitle : "";
|
||||||
|
printHeader.style.display = showHeaders ? "block" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printFooter) {
|
||||||
|
printFooter.innerHTML = showHeaders ? "Page 1" : "";
|
||||||
|
printFooter.style.display = showHeaders ? "block" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printContent && elements.editorContent) {
|
||||||
|
printContent.innerHTML = elements.editorContent.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printDocument() {
|
||||||
|
const orientation =
|
||||||
|
document.getElementById("printOrientation")?.value || "portrait";
|
||||||
|
const showHeaders = document.getElementById("printHeaders")?.checked;
|
||||||
|
const content = elements.editorContent?.innerHTML || "";
|
||||||
|
|
||||||
|
const printWindow = window.open("", "_blank");
|
||||||
|
|
||||||
|
printWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>${state.docTitle}</title>
|
||||||
|
<style>
|
||||||
|
@page { size: ${orientation}; margin: 1in; }
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 12pt;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
h1 { font-size: 24pt; margin-bottom: 12pt; }
|
||||||
|
h2 { font-size: 18pt; margin-bottom: 10pt; }
|
||||||
|
h3 { font-size: 14pt; margin-bottom: 8pt; }
|
||||||
|
p { margin-bottom: 12pt; }
|
||||||
|
table { border-collapse: collapse; width: 100%; margin: 12pt 0; }
|
||||||
|
td, th { border: 1px solid #ccc; padding: 8px; }
|
||||||
|
.page-break { page-break-after: always; }
|
||||||
|
${showHeaders ? `.header { text-align: center; font-size: 10pt; color: #666; margin-bottom: 24pt; }` : ""}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${showHeaders ? `<div class="header">${state.docTitle}</div>` : ""}
|
||||||
|
${content}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
|
||||||
|
printWindow.document.close();
|
||||||
|
printWindow.focus();
|
||||||
|
setTimeout(() => {
|
||||||
|
printWindow.print();
|
||||||
|
printWindow.close();
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
hideModal("printPreviewModal");
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertPageBreak() {
|
||||||
|
if (!elements.editorContent) return;
|
||||||
|
|
||||||
|
const pageBreak = document.createElement("div");
|
||||||
|
pageBreak.className = "page-break";
|
||||||
|
pageBreak.contentEditable = "false";
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection.rangeCount > 0) {
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
range.deleteContents();
|
||||||
|
range.insertNode(pageBreak);
|
||||||
|
|
||||||
|
const newParagraph = document.createElement("p");
|
||||||
|
newParagraph.innerHTML = "<br>";
|
||||||
|
pageBreak.after(newParagraph);
|
||||||
|
|
||||||
|
range.setStartAfter(newParagraph);
|
||||||
|
range.collapse(true);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
} else {
|
||||||
|
elements.editorContent.appendChild(pageBreak);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHeaderFooterModal() {
|
||||||
|
showModal("headerFooterModal");
|
||||||
|
|
||||||
|
const headerEditor = document.getElementById("headerEditor");
|
||||||
|
const footerEditor = document.getElementById("footerEditor");
|
||||||
|
|
||||||
|
if (headerEditor && elements.editorHeader) {
|
||||||
|
headerEditor.innerHTML = elements.editorHeader.innerHTML;
|
||||||
|
}
|
||||||
|
if (footerEditor && elements.editorFooter) {
|
||||||
|
footerEditor.innerHTML = elements.editorFooter.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchHfTab(tabName) {
|
||||||
|
document.querySelectorAll(".hf-tab").forEach((tab) => {
|
||||||
|
tab.classList.toggle("active", tab.dataset.tab === tabName);
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById("hfHeaderTab")
|
||||||
|
?.classList.toggle("active", tabName === "header");
|
||||||
|
document
|
||||||
|
.getElementById("hfFooterTab")
|
||||||
|
?.classList.toggle("active", tabName === "footer");
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertHfField(type, field) {
|
||||||
|
const editorId = type === "header" ? "headerEditor" : "footerEditor";
|
||||||
|
const editor = document.getElementById(editorId);
|
||||||
|
if (!editor) return;
|
||||||
|
|
||||||
|
let fieldContent = "";
|
||||||
|
switch (field) {
|
||||||
|
case "pageNum":
|
||||||
|
fieldContent =
|
||||||
|
'<span class="hf-field" data-field="pageNum">[Page #]</span>';
|
||||||
|
break;
|
||||||
|
case "date":
|
||||||
|
fieldContent = `<span class="hf-field" data-field="date">${new Date().toLocaleDateString()}</span>`;
|
||||||
|
break;
|
||||||
|
case "title":
|
||||||
|
fieldContent = `<span class="hf-field" data-field="title">${state.docTitle}</span>`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.focus();
|
||||||
|
document.execCommand("insertHTML", false, fieldContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyHeaderFooter() {
|
||||||
|
const headerEditor = document.getElementById("headerEditor");
|
||||||
|
const footerEditor = document.getElementById("footerEditor");
|
||||||
|
|
||||||
|
if (elements.editorHeader && headerEditor) {
|
||||||
|
elements.editorHeader.innerHTML = headerEditor.innerHTML;
|
||||||
|
}
|
||||||
|
if (elements.editorFooter && footerEditor) {
|
||||||
|
elements.editorFooter.innerHTML = footerEditor.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideModal("headerFooterModal");
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
addChatMessage("assistant", "Header and footer updated!");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeaderFooter() {
|
||||||
|
if (elements.editorHeader) {
|
||||||
|
elements.editorHeader.innerHTML = "";
|
||||||
|
}
|
||||||
|
if (elements.editorFooter) {
|
||||||
|
elements.editorFooter.innerHTML = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerEditor = document.getElementById("headerEditor");
|
||||||
|
const footerEditor = document.getElementById("footerEditor");
|
||||||
|
if (headerEditor) headerEditor.innerHTML = "";
|
||||||
|
if (footerEditor) footerEditor.innerHTML = "";
|
||||||
|
|
||||||
|
hideModal("headerFooterModal");
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
addChatMessage("assistant", "Header and footer removed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHeaderFooterInput() {
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
}
|
||||||
|
|
||||||
function createNewDocument() {
|
function createNewDocument() {
|
||||||
state.docId = null;
|
state.docId = null;
|
||||||
state.docTitle = "Untitled Document";
|
state.docTitle = "Untitled Document";
|
||||||
|
|
|
||||||
|
|
@ -1329,3 +1329,749 @@
|
||||||
border-color: #ccc !important;
|
border-color: #ccc !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
CHARTS & IMAGES DISPLAY
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.charts-container,
|
||||||
|
.images-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-md, 8px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
pointer-events: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrapper:hover {
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-wrapper.selected {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border-bottom: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-actions button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-actions button:hover {
|
||||||
|
background: var(--sentient-bg-tertiary, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-content {
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-canvas {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar {
|
||||||
|
flex: 1;
|
||||||
|
background: var(--sentient-accent, #4285f4);
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
min-width: 20px;
|
||||||
|
max-width: 60px;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar:nth-child(2) {
|
||||||
|
background: #34a853;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar:nth-child(3) {
|
||||||
|
background: #fbbc04;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar:nth-child(4) {
|
||||||
|
background: #ea4335;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-bar:nth-child(5) {
|
||||||
|
background: #9c27b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-line-container {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-line {
|
||||||
|
fill: none;
|
||||||
|
stroke: var(--sentient-accent, #4285f4);
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-line-point {
|
||||||
|
fill: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-pie-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-top: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-color {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: move;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper:hover {
|
||||||
|
border-color: var(--sentient-border, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper.selected {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: var(--sentient-accent, #4285f4);
|
||||||
|
border: 2px solid white;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: se-resize;
|
||||||
|
bottom: -5px;
|
||||||
|
right: -5px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper:hover .image-resize-handle,
|
||||||
|
.image-wrapper.selected .image-resize-handle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
FIND & REPLACE MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.find-replace-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-replace-group label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-replace-group input {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-replace-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-replace-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.find-results {
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
CONDITIONAL FORMATTING MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.cf-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-section label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-section input,
|
||||||
|
.cf-section select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-section input:focus,
|
||||||
|
.cf-section select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-values {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-values input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-style-options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-style-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-style-row label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-style-row input[type="color"] {
|
||||||
|
width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-style-row input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
accent-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-preview {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-preview label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf-preview-cell {
|
||||||
|
width: 120px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: #ffeb3b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
DATA VALIDATION MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.dv-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-tab {
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-tab:hover {
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-tab.active {
|
||||||
|
color: var(--sentient-accent, #4285f4);
|
||||||
|
border-bottom-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-section label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-section input,
|
||||||
|
.dv-section select,
|
||||||
|
.dv-section textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-section input:focus,
|
||||||
|
.dv-section select:focus,
|
||||||
|
.dv-section textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-section textarea {
|
||||||
|
min-height: 80px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-value-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-list {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
PRINT PREVIEW MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.modal-fullscreen {
|
||||||
|
width: 95vw;
|
||||||
|
max-width: 1400px;
|
||||||
|
height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-fullscreen .modal-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-fullscreen .modal-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-toolbar select {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 13px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-toolbar .checkbox-label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-preview-body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
background: var(--sentient-bg-tertiary, #e0e0e0);
|
||||||
|
overflow: auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-preview-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-page {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
padding: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-page.portrait {
|
||||||
|
width: 8.5in;
|
||||||
|
min-height: 11in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-page.landscape {
|
||||||
|
width: 11in;
|
||||||
|
min-height: 8.5in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-content {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-content table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-content td,
|
||||||
|
.print-content th {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 4px 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-content th {
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
CUSTOM NUMBER FORMAT MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.cnf-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cnf-section label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cnf-section input,
|
||||||
|
.cnf-section select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cnf-section input:focus,
|
||||||
|
.cnf-section select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cnf-preview {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: monospace;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cnf-formats-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 180px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cnf-format-item {
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cnf-format-item:hover {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cnf-format-item.selected {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
background: rgba(66, 133, 244, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
INSERT IMAGE MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.img-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-tab {
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-tab:hover {
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-tab.active {
|
||||||
|
color: var(--sentient-accent, #4285f4);
|
||||||
|
border-bottom-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-section label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-section input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-section input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-drop-zone {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 40px;
|
||||||
|
border: 2px dashed var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-md, 8px);
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-drop-zone:hover,
|
||||||
|
.img-drop-zone.dragover {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
background: rgba(66, 133, 244, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-drop-zone p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-preview-container {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-preview-container label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-preview-container img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 200px;
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
NUMBER FORMAT SELECT
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.number-format {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
UTILITY CLASSES
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,46 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="toolbar-divider"></span>
|
<span class="toolbar-divider"></span>
|
||||||
|
<div class="toolbar-group">
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="printPreviewBtn"
|
||||||
|
title="Print Preview"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||||
|
<path
|
||||||
|
d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
|
||||||
|
></path>
|
||||||
|
<rect x="6" y="14" width="12" height="8"></rect>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="findReplaceBtn"
|
||||||
|
title="Find & Replace (Ctrl+H)"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="toolbar-divider"></span>
|
||||||
<div class="toolbar-group">
|
<div class="toolbar-group">
|
||||||
<select
|
<select
|
||||||
class="toolbar-select font-family"
|
class="toolbar-select font-family"
|
||||||
|
|
@ -197,6 +237,66 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="toolbar-divider"></span>
|
<span class="toolbar-divider"></span>
|
||||||
|
<div class="toolbar-group">
|
||||||
|
<select
|
||||||
|
class="toolbar-select number-format"
|
||||||
|
id="numberFormat"
|
||||||
|
title="Number Format"
|
||||||
|
>
|
||||||
|
<option value="general">General</option>
|
||||||
|
<option value="number">Number (1,234.56)</option>
|
||||||
|
<option value="currency">Currency ($1,234.56)</option>
|
||||||
|
<option value="accounting">Accounting</option>
|
||||||
|
<option value="percent">Percent (12.34%)</option>
|
||||||
|
<option value="scientific">Scientific (1.23E+03)</option>
|
||||||
|
<option value="date_short">Date (1/15/2024)</option>
|
||||||
|
<option value="date_long">Date (January 15, 2024)</option>
|
||||||
|
<option value="time">Time (3:45 PM)</option>
|
||||||
|
<option value="datetime">Date & Time</option>
|
||||||
|
<option value="fraction">Fraction (1/4)</option>
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="custom">Custom...</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="decreaseDecimalBtn"
|
||||||
|
title="Decrease Decimal"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<text x="2" y="16" font-size="10" fill="currentColor">
|
||||||
|
.0
|
||||||
|
</text>
|
||||||
|
<path d="M18 8l-4 4 4 4"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="increaseDecimalBtn"
|
||||||
|
title="Increase Decimal"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<text x="2" y="16" font-size="10" fill="currentColor">
|
||||||
|
.00
|
||||||
|
</text>
|
||||||
|
<path d="M14 8l4 4-4 4"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="toolbar-divider"></span>
|
||||||
<div class="toolbar-group">
|
<div class="toolbar-group">
|
||||||
<button class="btn-icon" id="mergeCellsBtn" title="Merge Cells">
|
<button class="btn-icon" id="mergeCellsBtn" title="Merge Cells">
|
||||||
<svg
|
<svg
|
||||||
|
|
@ -216,17 +316,151 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
id="formatCurrencyBtn"
|
id="conditionalFormatBtn"
|
||||||
title="Format as Currency"
|
title="Conditional Formatting"
|
||||||
>
|
>
|
||||||
<span>$</span>
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
x="3"
|
||||||
|
y="3"
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
fill="#4CAF50"
|
||||||
|
></rect>
|
||||||
|
<rect
|
||||||
|
x="14"
|
||||||
|
y="3"
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
fill="#FFC107"
|
||||||
|
></rect>
|
||||||
|
<rect
|
||||||
|
x="3"
|
||||||
|
y="14"
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
fill="#F44336"
|
||||||
|
></rect>
|
||||||
|
<rect
|
||||||
|
x="14"
|
||||||
|
y="14"
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
fill="#2196F3"
|
||||||
|
></rect>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
id="formatPercentBtn"
|
id="dataValidationBtn"
|
||||||
title="Format as Percent"
|
title="Data Validation"
|
||||||
>
|
>
|
||||||
<span>%</span>
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M9 11l3 3L22 4"></path>
|
||||||
|
<path
|
||||||
|
d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="toolbar-divider"></span>
|
||||||
|
<div class="toolbar-group">
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="insertChartBtn"
|
||||||
|
title="Insert Chart"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect x="3" y="12" width="4" height="9"></rect>
|
||||||
|
<rect x="10" y="6" width="4" height="15"></rect>
|
||||||
|
<rect x="17" y="3" width="4" height="18"></rect>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="insertImageBtn"
|
||||||
|
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>
|
||||||
|
<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" id="filterBtn" title="Filter">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<polygon
|
||||||
|
points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"
|
||||||
|
></polygon>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn-icon" id="sortAscBtn" title="Sort A→Z">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M11 5h10M11 9h7M11 13h4"></path>
|
||||||
|
<path d="M3 17l3 3 3-3M6 18V4"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn-icon" id="sortDescBtn" title="Sort Z→A">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path d="M11 5h4M11 9h7M11 13h10"></path>
|
||||||
|
<path d="M3 7l3-3 3 3M6 6v12"></path>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -297,6 +531,14 @@
|
||||||
class="cursor-indicators"
|
class="cursor-indicators"
|
||||||
id="cursorIndicators"
|
id="cursorIndicators"
|
||||||
></div>
|
></div>
|
||||||
|
<div
|
||||||
|
class="charts-container"
|
||||||
|
id="chartsContainer"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="images-container"
|
||||||
|
id="imagesContainer"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -598,7 +840,7 @@
|
||||||
<button class="btn-secondary" id="cancelChartBtn">
|
<button class="btn-secondary" id="cancelChartBtn">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-primary" id="insertChartBtn">
|
<button class="btn-primary" id="insertChartBtnConfirm">
|
||||||
Insert Chart
|
Insert Chart
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -606,4 +848,464 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="findReplaceModal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Find & Replace</h3>
|
||||||
|
<button class="btn-close" id="closeFindReplaceModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="find-replace-group">
|
||||||
|
<label>Find:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="findInput"
|
||||||
|
placeholder="Search text..."
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="find-replace-group">
|
||||||
|
<label>Replace:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="replaceInput"
|
||||||
|
placeholder="Replace with..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="find-replace-options">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="findMatchCase" />
|
||||||
|
Match case
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="findWholeCell" />
|
||||||
|
Match entire cell contents
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="findRegex" />
|
||||||
|
Use regular expressions
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="find-results" id="findResults">
|
||||||
|
<span>0 matches found</span>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="findPrevBtn">Previous</button>
|
||||||
|
<button class="btn-secondary" id="findNextBtn">Next</button>
|
||||||
|
<button class="btn-primary" id="replaceBtn">Replace</button>
|
||||||
|
<button class="btn-primary" id="replaceAllBtn">
|
||||||
|
Replace All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="conditionalFormatModal">
|
||||||
|
<div class="modal-content modal-large">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Conditional Formatting</h3>
|
||||||
|
<button class="btn-close" id="closeConditionalFormatModal">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="cf-section">
|
||||||
|
<label>Apply to range:</label>
|
||||||
|
<input type="text" id="cfRange" placeholder="e.g., A1:D10" />
|
||||||
|
</div>
|
||||||
|
<div class="cf-section">
|
||||||
|
<label>Format cells if:</label>
|
||||||
|
<select id="cfRuleType">
|
||||||
|
<option value="greater_than">Greater than</option>
|
||||||
|
<option value="less_than">Less than</option>
|
||||||
|
<option value="equal_to">Equal to</option>
|
||||||
|
<option value="between">Between</option>
|
||||||
|
<option value="text_contains">Text contains</option>
|
||||||
|
<option value="text_starts">Text starts with</option>
|
||||||
|
<option value="text_ends">Text ends with</option>
|
||||||
|
<option value="duplicate">Duplicate values</option>
|
||||||
|
<option value="unique">Unique values</option>
|
||||||
|
<option value="blank">Is blank</option>
|
||||||
|
<option value="not_blank">Is not blank</option>
|
||||||
|
<option value="top_n">Top N values</option>
|
||||||
|
<option value="bottom_n">Bottom N values</option>
|
||||||
|
<option value="above_average">Above average</option>
|
||||||
|
<option value="below_average">Below average</option>
|
||||||
|
<option value="color_scale">Color scale</option>
|
||||||
|
<option value="data_bar">Data bars</option>
|
||||||
|
<option value="icon_set">Icon set</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="cf-section cf-values" id="cfValuesSection">
|
||||||
|
<input type="text" id="cfValue1" placeholder="Value" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="cfValue2"
|
||||||
|
placeholder="and"
|
||||||
|
class="hidden"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="cf-section">
|
||||||
|
<label>Formatting style:</label>
|
||||||
|
<div class="cf-style-options">
|
||||||
|
<div class="cf-style-row">
|
||||||
|
<label>Background:</label>
|
||||||
|
<input type="color" id="cfBgColor" value="#ffeb3b" />
|
||||||
|
</div>
|
||||||
|
<div class="cf-style-row">
|
||||||
|
<label>Text color:</label>
|
||||||
|
<input type="color" id="cfTextColor" value="#000000" />
|
||||||
|
</div>
|
||||||
|
<div class="cf-style-row">
|
||||||
|
<label>Bold:</label>
|
||||||
|
<input type="checkbox" id="cfBold" />
|
||||||
|
</div>
|
||||||
|
<div class="cf-style-row">
|
||||||
|
<label>Italic:</label>
|
||||||
|
<input type="checkbox" id="cfItalic" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cf-preview">
|
||||||
|
<label>Preview:</label>
|
||||||
|
<div class="cf-preview-cell" id="cfPreviewCell">Sample</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="cancelCfBtn">Cancel</button>
|
||||||
|
<button class="btn-primary" id="applyCfBtn">Apply</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="dataValidationModal">
|
||||||
|
<div class="modal-content modal-large">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Data Validation</h3>
|
||||||
|
<button class="btn-close" id="closeDataValidationModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="dv-tabs">
|
||||||
|
<button class="dv-tab active" data-tab="settings">
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
|
<button class="dv-tab" data-tab="input">Input Message</button>
|
||||||
|
<button class="dv-tab" data-tab="error">Error Alert</button>
|
||||||
|
</div>
|
||||||
|
<div class="dv-tab-content active" id="dvSettingsTab">
|
||||||
|
<div class="dv-section">
|
||||||
|
<label>Apply to range:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="dvRange"
|
||||||
|
placeholder="e.g., A1:A100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section">
|
||||||
|
<label>Allow:</label>
|
||||||
|
<select id="dvType">
|
||||||
|
<option value="any">Any value</option>
|
||||||
|
<option value="whole_number">Whole number</option>
|
||||||
|
<option value="decimal">Decimal</option>
|
||||||
|
<option value="list">List</option>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
<option value="time">Time</option>
|
||||||
|
<option value="text_length">Text length</option>
|
||||||
|
<option value="custom">Custom formula</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section dv-criteria" id="dvCriteriaSection">
|
||||||
|
<label>Data:</label>
|
||||||
|
<select id="dvOperator">
|
||||||
|
<option value="between">between</option>
|
||||||
|
<option value="not_between">not between</option>
|
||||||
|
<option value="equal">equal to</option>
|
||||||
|
<option value="not_equal">not equal to</option>
|
||||||
|
<option value="greater">greater than</option>
|
||||||
|
<option value="less">less than</option>
|
||||||
|
<option value="greater_equal">
|
||||||
|
greater than or equal to
|
||||||
|
</option>
|
||||||
|
<option value="less_equal">
|
||||||
|
less than or equal to
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section dv-values" id="dvValuesSection">
|
||||||
|
<div class="dv-value-row">
|
||||||
|
<label id="dvValue1Label">Minimum:</label>
|
||||||
|
<input type="text" id="dvValue1" />
|
||||||
|
</div>
|
||||||
|
<div class="dv-value-row" id="dvValue2Row">
|
||||||
|
<label>Maximum:</label>
|
||||||
|
<input type="text" id="dvValue2" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section dv-list hidden" id="dvListSection">
|
||||||
|
<label>Source (comma-separated):</label>
|
||||||
|
<textarea
|
||||||
|
id="dvListSource"
|
||||||
|
placeholder="Option 1, Option 2, Option 3"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="dvIgnoreBlank" checked />
|
||||||
|
Ignore blank
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="dvShowDropdown" checked />
|
||||||
|
In-cell dropdown
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dv-tab-content" id="dvInputTab">
|
||||||
|
<div class="dv-section">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="dvShowInput" checked />
|
||||||
|
Show input message when cell is selected
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section">
|
||||||
|
<label>Title:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="dvInputTitle"
|
||||||
|
placeholder="Input title"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section">
|
||||||
|
<label>Input message:</label>
|
||||||
|
<textarea
|
||||||
|
id="dvInputMessage"
|
||||||
|
placeholder="Enter instructions for the user"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dv-tab-content" id="dvErrorTab">
|
||||||
|
<div class="dv-section">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="dvShowError" checked />
|
||||||
|
Show error alert after invalid data is entered
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section">
|
||||||
|
<label>Style:</label>
|
||||||
|
<select id="dvErrorStyle">
|
||||||
|
<option value="stop">Stop</option>
|
||||||
|
<option value="warning">Warning</option>
|
||||||
|
<option value="information">Information</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section">
|
||||||
|
<label>Title:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="dvErrorTitle"
|
||||||
|
placeholder="Error title"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="dv-section">
|
||||||
|
<label>Error message:</label>
|
||||||
|
<textarea
|
||||||
|
id="dvErrorMessage"
|
||||||
|
placeholder="Enter error message"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="clearDvBtn">Clear All</button>
|
||||||
|
<button class="btn-secondary" id="cancelDvBtn">Cancel</button>
|
||||||
|
<button class="btn-primary" id="applyDvBtn">OK</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="printPreviewModal">
|
||||||
|
<div class="modal-content modal-fullscreen">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Print Preview</h3>
|
||||||
|
<div class="print-toolbar">
|
||||||
|
<select id="printOrientation">
|
||||||
|
<option value="portrait">Portrait</option>
|
||||||
|
<option value="landscape">Landscape</option>
|
||||||
|
</select>
|
||||||
|
<select id="printPaperSize">
|
||||||
|
<option value="letter">Letter (8.5" x 11")</option>
|
||||||
|
<option value="a4">A4 (210mm x 297mm)</option>
|
||||||
|
<option value="legal">Legal (8.5" x 14")</option>
|
||||||
|
<option value="tabloid">Tabloid (11" x 17")</option>
|
||||||
|
</select>
|
||||||
|
<select id="printScale">
|
||||||
|
<option value="100">100%</option>
|
||||||
|
<option value="fit_width">Fit to width</option>
|
||||||
|
<option value="fit_page">Fit to page</option>
|
||||||
|
<option value="75">75%</option>
|
||||||
|
<option value="50">50%</option>
|
||||||
|
</select>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="printGridlines" />
|
||||||
|
Gridlines
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="printHeaders" />
|
||||||
|
Row/Column headers
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn-close" id="closePrintPreviewModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body print-preview-body">
|
||||||
|
<div class="print-preview-container" id="printPreviewContainer">
|
||||||
|
<div class="print-page" id="printPage">
|
||||||
|
<div class="print-content" id="printContent"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="cancelPrintBtn">Cancel</button>
|
||||||
|
<button class="btn-primary" id="printBtn">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 6 2 18 2 18 9"></polyline>
|
||||||
|
<path
|
||||||
|
d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
|
||||||
|
></path>
|
||||||
|
<rect x="6" y="14" width="12" height="8"></rect>
|
||||||
|
</svg>
|
||||||
|
Print
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="customNumberFormatModal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Custom Number Format</h3>
|
||||||
|
<button class="btn-close" id="closeCustomFormatModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="cnf-section">
|
||||||
|
<label>Category:</label>
|
||||||
|
<select id="cnfCategory">
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="currency">Currency</option>
|
||||||
|
<option value="date">Date</option>
|
||||||
|
<option value="time">Time</option>
|
||||||
|
<option value="percentage">Percentage</option>
|
||||||
|
<option value="fraction">Fraction</option>
|
||||||
|
<option value="scientific">Scientific</option>
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="custom">Custom</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="cnf-section">
|
||||||
|
<label>Format code:</label>
|
||||||
|
<input type="text" id="cnfFormatCode" placeholder="#,##0.00" />
|
||||||
|
</div>
|
||||||
|
<div class="cnf-section">
|
||||||
|
<label>Preview:</label>
|
||||||
|
<div class="cnf-preview" id="cnfPreview">1,234.56</div>
|
||||||
|
</div>
|
||||||
|
<div class="cnf-section">
|
||||||
|
<label>Common formats:</label>
|
||||||
|
<div class="cnf-formats-list" id="cnfFormatsList">
|
||||||
|
<div class="cnf-format-item" data-format="#,##0">1,235</div>
|
||||||
|
<div class="cnf-format-item" data-format="#,##0.00">
|
||||||
|
1,234.56
|
||||||
|
</div>
|
||||||
|
<div class="cnf-format-item" data-format="$#,##0.00">
|
||||||
|
$1,234.56
|
||||||
|
</div>
|
||||||
|
<div class="cnf-format-item" data-format="0%">12%</div>
|
||||||
|
<div class="cnf-format-item" data-format="0.00%">
|
||||||
|
12.34%
|
||||||
|
</div>
|
||||||
|
<div class="cnf-format-item" data-format="0.00E+00">
|
||||||
|
1.23E+03
|
||||||
|
</div>
|
||||||
|
<div class="cnf-format-item" data-format="MM/DD/YYYY">
|
||||||
|
01/15/2024
|
||||||
|
</div>
|
||||||
|
<div class="cnf-format-item" data-format="MMMM D, YYYY">
|
||||||
|
January 15, 2024
|
||||||
|
</div>
|
||||||
|
<div class="cnf-format-item" data-format="HH:MM:SS">
|
||||||
|
14:30:00
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="cancelCnfBtn">Cancel</button>
|
||||||
|
<button class="btn-primary" id="applyCnfBtn">Apply</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="insertImageModal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Insert Image</h3>
|
||||||
|
<button class="btn-close" id="closeInsertImageModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="img-tabs">
|
||||||
|
<button class="img-tab active" data-tab="url">From URL</button>
|
||||||
|
<button class="img-tab" data-tab="upload">Upload</button>
|
||||||
|
</div>
|
||||||
|
<div class="img-tab-content active" id="imgUrlTab">
|
||||||
|
<div class="img-section">
|
||||||
|
<label>Image URL:</label>
|
||||||
|
<input type="text" id="imgUrl" placeholder="https://..." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="img-tab-content" id="imgUploadTab">
|
||||||
|
<div class="img-section">
|
||||||
|
<label>Select file:</label>
|
||||||
|
<input type="file" id="imgFile" accept="image/*" />
|
||||||
|
</div>
|
||||||
|
<div class="img-drop-zone" id="imgDropZone">
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
x="3"
|
||||||
|
y="3"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
rx="2"
|
||||||
|
ry="2"
|
||||||
|
></rect>
|
||||||
|
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||||||
|
<polyline points="21 15 16 10 5 21"></polyline>
|
||||||
|
</svg>
|
||||||
|
<p>Drag and drop an image here</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="img-preview-container hidden" id="imgPreviewContainer">
|
||||||
|
<label>Preview:</label>
|
||||||
|
<img id="imgPreview" src="" alt="Preview" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="cancelImgBtn">Cancel</button>
|
||||||
|
<button class="btn-primary" id="insertImgBtn">Insert</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="sheet/sheet.js"></script>
|
<script src="sheet/sheet.js"></script>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -118,7 +118,9 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--sentient-text-secondary, #666666);
|
color: var(--sentient-text-secondary, #666666);
|
||||||
transition: background 0.15s, color 0.15s;
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon:hover {
|
.btn-icon:hover {
|
||||||
|
|
@ -302,7 +304,9 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
transition: border-color 0.15s, box-shadow 0.15s;
|
transition:
|
||||||
|
border-color 0.15s,
|
||||||
|
box-shadow 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-thumbnail:hover {
|
.slide-thumbnail:hover {
|
||||||
|
|
@ -349,7 +353,10 @@
|
||||||
color: var(--sentient-text-secondary, #666666);
|
color: var(--sentient-text-secondary, #666666);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
border-color 0.15s,
|
||||||
|
color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-add-slide:hover {
|
.btn-add-slide:hover {
|
||||||
|
|
@ -453,14 +460,50 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.handle.nw { top: -5px; left: -5px; cursor: nwse-resize; }
|
.handle.nw {
|
||||||
.handle.n { top: -5px; left: 50%; transform: translateX(-50%); cursor: ns-resize; }
|
top: -5px;
|
||||||
.handle.ne { top: -5px; right: -5px; cursor: nesw-resize; }
|
left: -5px;
|
||||||
.handle.w { top: 50%; left: -5px; transform: translateY(-50%); cursor: ew-resize; }
|
cursor: nwse-resize;
|
||||||
.handle.e { top: 50%; right: -5px; transform: translateY(-50%); cursor: ew-resize; }
|
}
|
||||||
.handle.sw { bottom: -5px; left: -5px; cursor: nesw-resize; }
|
.handle.n {
|
||||||
.handle.s { bottom: -5px; left: 50%; transform: translateX(-50%); cursor: ns-resize; }
|
top: -5px;
|
||||||
.handle.se { bottom: -5px; right: -5px; cursor: nwse-resize; }
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
.handle.ne {
|
||||||
|
top: -5px;
|
||||||
|
right: -5px;
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
.handle.w {
|
||||||
|
top: 50%;
|
||||||
|
left: -5px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
.handle.e {
|
||||||
|
top: 50%;
|
||||||
|
right: -5px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
.handle.sw {
|
||||||
|
bottom: -5px;
|
||||||
|
left: -5px;
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
.handle.s {
|
||||||
|
bottom: -5px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
.handle.se {
|
||||||
|
bottom: -5px;
|
||||||
|
right: -5px;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
|
||||||
.rotate-handle {
|
.rotate-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -497,7 +540,9 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
transition: left 0.1s, top 0.1s;
|
transition:
|
||||||
|
left 0.1s,
|
||||||
|
top 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor-indicator::after {
|
.cursor-indicator::after {
|
||||||
|
|
@ -524,7 +569,9 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transition: width 0.2s ease, opacity 0.2s ease;
|
transition:
|
||||||
|
width 0.2s ease,
|
||||||
|
opacity 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-panel.collapsed {
|
.chat-panel.collapsed {
|
||||||
|
|
@ -663,7 +710,10 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--sentient-text-secondary, #666666);
|
color: var(--sentient-text-secondary, #666666);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
border-color 0.15s,
|
||||||
|
color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestion-btn:hover {
|
.suggestion-btn:hover {
|
||||||
|
|
@ -932,7 +982,10 @@
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--sentient-text-secondary, #666666);
|
color: var(--sentient-text-secondary, #666666);
|
||||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
border-color 0.15s,
|
||||||
|
color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shape-btn:hover {
|
.shape-btn:hover {
|
||||||
|
|
@ -1034,3 +1087,731 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
TRANSITIONS MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.transitions-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transitions-section label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transitions-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-btn:hover {
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
border-color: var(--sentient-border, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-btn.active {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
background: rgba(66, 133, 244, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-btn span {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-preview {
|
||||||
|
width: 48px;
|
||||||
|
height: 32px;
|
||||||
|
background: var(--sentient-accent, #4285f4);
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-preview::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(255, 255, 255, 0.3) 0%,
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-control input[type="range"] {
|
||||||
|
flex: 1;
|
||||||
|
height: 4px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-control input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: var(--sentient-accent, #4285f4);
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-control span {
|
||||||
|
min-width: 40px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
ANIMATIONS MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.animations-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animations-section label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animations-section select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animations-section select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-element-info {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-timing {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-group label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-group select,
|
||||||
|
.timing-group input[type="number"] {
|
||||||
|
width: auto;
|
||||||
|
min-width: 80px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-group span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-order-list {
|
||||||
|
min-height: 100px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-order-list .no-animations {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--sentient-text-muted, #999);
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 24px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-item:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-item .animation-name {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-item .animation-element {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-item .animation-remove {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: var(--sentient-text-muted, #999);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-item .animation-remove:hover {
|
||||||
|
color: var(--sentient-error, #ea4335);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
MODAL LARGE
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.modal-content.modal-large {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content.modal-fullscreen {
|
||||||
|
width: 95vw;
|
||||||
|
max-width: 1400px;
|
||||||
|
height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-fullscreen .modal-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-fullscreen .modal-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
SLIDE SORTER VIEW
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.sorter-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-body {
|
||||||
|
display: flex;
|
||||||
|
background: var(--sentient-bg-tertiary, #e0e0e0);
|
||||||
|
overflow: auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
width: 100%;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
background: white;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: grab;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide:hover {
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide.selected {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
box-shadow: 0 0 0 3px rgba(66, 133, 244, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide.drag-over {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
background: rgba(66, 133, 244, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
transform-origin: top left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide-number {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide:hover .sorter-slide-actions {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide-actions button {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: none;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-slide-actions button:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
EXPORT PDF MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.export-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-section label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-section select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-section select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-range-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label input[type="radio"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-input {
|
||||||
|
margin-left: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 13px;
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.transitions-grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-timing {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timing-group select,
|
||||||
|
.timing-group input[type="number"] {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-toolbar {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =============================================================================
|
||||||
|
MASTER SLIDE MODAL
|
||||||
|
============================================================================= */
|
||||||
|
|
||||||
|
.master-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-sidebar {
|
||||||
|
width: 200px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-right: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-sidebar h4 {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-layout-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-layout-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-layout-item:hover {
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
border-color: var(--sentient-border, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-layout-item.active {
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
background: rgba(66, 133, 244, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-layout-item span {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-preview {
|
||||||
|
width: 80px;
|
||||||
|
height: 45px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-title {
|
||||||
|
height: 8px;
|
||||||
|
background: var(--sentient-text-primary, #212121);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-title.small {
|
||||||
|
height: 5px;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-subtitle {
|
||||||
|
height: 5px;
|
||||||
|
width: 50%;
|
||||||
|
background: var(--sentient-text-secondary, #666);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
flex: 1;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-columns {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-col {
|
||||||
|
flex: 1;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-section-title {
|
||||||
|
height: 10px;
|
||||||
|
width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
background: var(--sentient-text-primary, #212121);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blank-preview {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-editor {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-section h4 {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-item label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-item input[type="color"] {
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
padding: 2px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-item label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--sentient-text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-item select {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
border-radius: var(--sentient-radius-sm, 4px);
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--sentient-bg-primary, #ffffff);
|
||||||
|
color: var(--sentient-text-primary, #212121);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-item select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--sentient-accent, #4285f4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-preview {
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--sentient-bg-secondary, #f5f5f5);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-slide {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-heading {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-body {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.master-layout {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid var(--sentient-border, #e0e0e0);
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-layout-list {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master-layout-item {
|
||||||
|
width: calc(33.33% - 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,46 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span class="toolbar-divider"></span>
|
<span class="toolbar-divider"></span>
|
||||||
|
<div class="toolbar-group">
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="transitionsBtn"
|
||||||
|
title="Slide Transitions"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect x="2" y="3" width="8" height="14" rx="1"></rect>
|
||||||
|
<rect x="14" y="7" width="8" height="14" rx="1"></rect>
|
||||||
|
<path d="M10 10l4 4m0-4l-4 4"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn-icon" id="animationsBtn" title="Animations">
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
<path d="M12 2v4m0 12v4m10-10h-4M6 12H2"></path>
|
||||||
|
<path
|
||||||
|
d="M19.07 4.93l-2.83 2.83m-8.48 8.48l-2.83 2.83"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="toolbar-divider"></span>
|
||||||
<div class="toolbar-group">
|
<div class="toolbar-group">
|
||||||
<select
|
<select
|
||||||
class="toolbar-select font-family"
|
class="toolbar-select font-family"
|
||||||
|
|
@ -250,6 +290,63 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-right">
|
<div class="toolbar-right">
|
||||||
<div class="collaborators" id="collaborators"></div>
|
<div class="collaborators" id="collaborators"></div>
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="masterSlideBtn"
|
||||||
|
title="Edit Master Slide"
|
||||||
|
>
|
||||||
|
<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="14" rx="2"></rect>
|
||||||
|
<path d="M3 7h18"></path>
|
||||||
|
<circle cx="7" cy="5" r="1" fill="currentColor"></circle>
|
||||||
|
<circle cx="10" cy="5" r="1" fill="currentColor"></circle>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
id="slideSorterBtn"
|
||||||
|
title="Slide Sorter View"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<rect x="3" y="3" width="7" height="5" rx="1"></rect>
|
||||||
|
<rect x="14" y="3" width="7" height="5" rx="1"></rect>
|
||||||
|
<rect x="3" y="10" width="7" height="5" rx="1"></rect>
|
||||||
|
<rect x="14" y="10" width="7" height="5" rx="1"></rect>
|
||||||
|
<rect x="3" y="17" width="7" height="5" rx="1"></rect>
|
||||||
|
<rect x="14" y="17" width="7" height="5" rx="1"></rect>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="btn-icon" id="exportPdfBtn" title="Export to PDF">
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
|
||||||
|
></path>
|
||||||
|
<polyline points="14 2 14 8 20 8"></polyline>
|
||||||
|
<path d="M9 15h6"></path>
|
||||||
|
<path d="M12 12v6"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button class="btn-icon" id="presentBtn" title="Present (F5)">
|
<button class="btn-icon" id="presentBtn" title="Present (F5)">
|
||||||
<svg
|
<svg
|
||||||
width="18"
|
width="18"
|
||||||
|
|
@ -805,4 +902,524 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="transitionsModal">
|
||||||
|
<div class="modal-content modal-large">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Slide Transitions</h3>
|
||||||
|
<button class="btn-close" id="closeTransitionsModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="transitions-section">
|
||||||
|
<label>Transition Effect:</label>
|
||||||
|
<div class="transitions-grid">
|
||||||
|
<button
|
||||||
|
class="transition-btn active"
|
||||||
|
data-transition="none"
|
||||||
|
title="None"
|
||||||
|
>
|
||||||
|
<div class="transition-preview none"></div>
|
||||||
|
<span>None</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="fade"
|
||||||
|
title="Fade"
|
||||||
|
>
|
||||||
|
<div class="transition-preview fade"></div>
|
||||||
|
<span>Fade</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="slide-left"
|
||||||
|
title="Slide Left"
|
||||||
|
>
|
||||||
|
<div class="transition-preview slide-left"></div>
|
||||||
|
<span>Slide Left</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="slide-right"
|
||||||
|
title="Slide Right"
|
||||||
|
>
|
||||||
|
<div class="transition-preview slide-right"></div>
|
||||||
|
<span>Slide Right</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="slide-up"
|
||||||
|
title="Slide Up"
|
||||||
|
>
|
||||||
|
<div class="transition-preview slide-up"></div>
|
||||||
|
<span>Slide Up</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="slide-down"
|
||||||
|
title="Slide Down"
|
||||||
|
>
|
||||||
|
<div class="transition-preview slide-down"></div>
|
||||||
|
<span>Slide Down</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="zoom-in"
|
||||||
|
title="Zoom In"
|
||||||
|
>
|
||||||
|
<div class="transition-preview zoom-in"></div>
|
||||||
|
<span>Zoom In</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="zoom-out"
|
||||||
|
title="Zoom Out"
|
||||||
|
>
|
||||||
|
<div class="transition-preview zoom-out"></div>
|
||||||
|
<span>Zoom Out</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="flip"
|
||||||
|
title="Flip"
|
||||||
|
>
|
||||||
|
<div class="transition-preview flip"></div>
|
||||||
|
<span>Flip</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="transition-btn"
|
||||||
|
data-transition="cube"
|
||||||
|
title="Cube"
|
||||||
|
>
|
||||||
|
<div class="transition-preview cube"></div>
|
||||||
|
<span>Cube</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="transitions-section">
|
||||||
|
<label>Duration:</label>
|
||||||
|
<div class="duration-control">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
id="transitionDuration"
|
||||||
|
min="0.1"
|
||||||
|
max="3"
|
||||||
|
step="0.1"
|
||||||
|
value="0.5"
|
||||||
|
/>
|
||||||
|
<span id="durationValue">0.5s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="transitions-section">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="applyToAllSlides" />
|
||||||
|
Apply to all slides
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="cancelTransitionsBtn">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button class="btn-primary" id="applyTransitionsBtn">
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="animationsModal">
|
||||||
|
<div class="modal-content modal-large">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Animations</h3>
|
||||||
|
<button class="btn-close" id="closeAnimationsModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="animations-section">
|
||||||
|
<label>Select element on slide to animate</label>
|
||||||
|
<div class="selected-element-info" id="selectedElementInfo">
|
||||||
|
No element selected
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="animations-section">
|
||||||
|
<label>Entrance Animation:</label>
|
||||||
|
<select id="entranceAnimation">
|
||||||
|
<option value="none">None</option>
|
||||||
|
<option value="fade-in">Fade In</option>
|
||||||
|
<option value="fly-in-left">Fly In (Left)</option>
|
||||||
|
<option value="fly-in-right">Fly In (Right)</option>
|
||||||
|
<option value="fly-in-top">Fly In (Top)</option>
|
||||||
|
<option value="fly-in-bottom">Fly In (Bottom)</option>
|
||||||
|
<option value="zoom-in">Zoom In</option>
|
||||||
|
<option value="bounce-in">Bounce In</option>
|
||||||
|
<option value="spin-in">Spin In</option>
|
||||||
|
<option value="wipe-left">Wipe (Left)</option>
|
||||||
|
<option value="wipe-right">Wipe (Right)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="animations-section">
|
||||||
|
<label>Emphasis Animation:</label>
|
||||||
|
<select id="emphasisAnimation">
|
||||||
|
<option value="none">None</option>
|
||||||
|
<option value="pulse">Pulse</option>
|
||||||
|
<option value="shake">Shake</option>
|
||||||
|
<option value="bounce">Bounce</option>
|
||||||
|
<option value="spin">Spin</option>
|
||||||
|
<option value="grow">Grow/Shrink</option>
|
||||||
|
<option value="flash">Flash</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="animations-section">
|
||||||
|
<label>Exit Animation:</label>
|
||||||
|
<select id="exitAnimation">
|
||||||
|
<option value="none">None</option>
|
||||||
|
<option value="fade-out">Fade Out</option>
|
||||||
|
<option value="fly-out-left">Fly Out (Left)</option>
|
||||||
|
<option value="fly-out-right">Fly Out (Right)</option>
|
||||||
|
<option value="fly-out-top">Fly Out (Top)</option>
|
||||||
|
<option value="fly-out-bottom">Fly Out (Bottom)</option>
|
||||||
|
<option value="zoom-out">Zoom Out</option>
|
||||||
|
<option value="spin-out">Spin Out</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="animations-section">
|
||||||
|
<div class="animation-timing">
|
||||||
|
<div class="timing-group">
|
||||||
|
<label>Start:</label>
|
||||||
|
<select id="animationStart">
|
||||||
|
<option value="on-click">On Click</option>
|
||||||
|
<option value="with-previous">With Previous</option>
|
||||||
|
<option value="after-previous">
|
||||||
|
After Previous
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="timing-group">
|
||||||
|
<label>Duration:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="animationDuration"
|
||||||
|
value="0.5"
|
||||||
|
min="0.1"
|
||||||
|
max="5"
|
||||||
|
step="0.1"
|
||||||
|
/>
|
||||||
|
<span>sec</span>
|
||||||
|
</div>
|
||||||
|
<div class="timing-group">
|
||||||
|
<label>Delay:</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="animationDelay"
|
||||||
|
value="0"
|
||||||
|
min="0"
|
||||||
|
max="10"
|
||||||
|
step="0.1"
|
||||||
|
/>
|
||||||
|
<span>sec</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="animations-section">
|
||||||
|
<label>Animation Order:</label>
|
||||||
|
<div class="animation-order-list" id="animationOrderList">
|
||||||
|
<p class="no-animations">No animations added yet</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="previewAnimationBtn">
|
||||||
|
Preview
|
||||||
|
</button>
|
||||||
|
<button class="btn-secondary" id="cancelAnimationsBtn">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button class="btn-primary" id="applyAnimationsBtn">
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="slideSorterModal">
|
||||||
|
<div class="modal-content modal-fullscreen">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Slide Sorter</h3>
|
||||||
|
<div class="sorter-toolbar">
|
||||||
|
<button class="btn-secondary" id="sorterAddSlide">
|
||||||
|
<svg
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||||
|
</svg>
|
||||||
|
Add Slide
|
||||||
|
</button>
|
||||||
|
<button class="btn-secondary" id="sorterDuplicateSlide">
|
||||||
|
Duplicate
|
||||||
|
</button>
|
||||||
|
<button class="btn-secondary" id="sorterDeleteSlide">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="btn-close" id="closeSlideSorterModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body sorter-body">
|
||||||
|
<div class="sorter-grid" id="sorterGrid"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="cancelSorterBtn">Cancel</button>
|
||||||
|
<button class="btn-primary" id="applySorterBtn">Apply</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="exportPdfModal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Export to PDF</h3>
|
||||||
|
<button class="btn-close" id="closeExportPdfModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="export-section">
|
||||||
|
<label>Slide Range:</label>
|
||||||
|
<div class="export-range-options">
|
||||||
|
<label class="radio-label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="slideRange"
|
||||||
|
value="all"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
All slides
|
||||||
|
</label>
|
||||||
|
<label class="radio-label">
|
||||||
|
<input type="radio" name="slideRange" value="current" />
|
||||||
|
Current slide
|
||||||
|
</label>
|
||||||
|
<label class="radio-label">
|
||||||
|
<input type="radio" name="slideRange" value="custom" />
|
||||||
|
Custom range:
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="customRange"
|
||||||
|
placeholder="e.g., 1-5, 8"
|
||||||
|
class="range-input"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="export-section">
|
||||||
|
<label>Layout:</label>
|
||||||
|
<select id="pdfLayout">
|
||||||
|
<option value="full">Full page slides</option>
|
||||||
|
<option value="notes">Slides with notes</option>
|
||||||
|
<option value="handout-2">Handouts (2 per page)</option>
|
||||||
|
<option value="handout-4">Handouts (4 per page)</option>
|
||||||
|
<option value="handout-6">Handouts (6 per page)</option>
|
||||||
|
<option value="outline">Outline only</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="export-section">
|
||||||
|
<label>Orientation:</label>
|
||||||
|
<select id="pdfOrientation">
|
||||||
|
<option value="landscape">Landscape</option>
|
||||||
|
<option value="portrait">Portrait</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="export-section">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="pdfIncludeHidden" />
|
||||||
|
Include hidden slides
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="cancelExportPdfBtn">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button class="btn-primary" id="exportPdfBtnConfirm">
|
||||||
|
Export PDF
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal hidden" id="masterSlideModal">
|
||||||
|
<div class="modal-content modal-large">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Edit Master Slide</h3>
|
||||||
|
<button class="btn-close" id="closeMasterSlideModal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="master-layout">
|
||||||
|
<div class="master-sidebar">
|
||||||
|
<h4>Master Layouts</h4>
|
||||||
|
<div class="master-layout-list" id="masterLayoutList">
|
||||||
|
<div
|
||||||
|
class="master-layout-item active"
|
||||||
|
data-layout="title"
|
||||||
|
>
|
||||||
|
<div class="layout-preview title-preview">
|
||||||
|
<div class="preview-title"></div>
|
||||||
|
<div class="preview-subtitle"></div>
|
||||||
|
</div>
|
||||||
|
<span>Title Slide</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="master-layout-item"
|
||||||
|
data-layout="title_content"
|
||||||
|
>
|
||||||
|
<div class="layout-preview title-content-preview">
|
||||||
|
<div class="preview-title small"></div>
|
||||||
|
<div class="preview-content"></div>
|
||||||
|
</div>
|
||||||
|
<span>Title and Content</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="master-layout-item"
|
||||||
|
data-layout="two_content"
|
||||||
|
>
|
||||||
|
<div class="layout-preview two-content-preview">
|
||||||
|
<div class="preview-title small"></div>
|
||||||
|
<div class="preview-columns">
|
||||||
|
<div class="preview-col"></div>
|
||||||
|
<div class="preview-col"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span>Two Content</span>
|
||||||
|
</div>
|
||||||
|
<div class="master-layout-item" data-layout="section">
|
||||||
|
<div class="layout-preview section-preview">
|
||||||
|
<div class="preview-section-title"></div>
|
||||||
|
</div>
|
||||||
|
<span>Section Header</span>
|
||||||
|
</div>
|
||||||
|
<div class="master-layout-item" data-layout="blank">
|
||||||
|
<div class="layout-preview blank-preview"></div>
|
||||||
|
<span>Blank</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="master-editor">
|
||||||
|
<div class="master-section">
|
||||||
|
<h4>Theme Colors</h4>
|
||||||
|
<div class="color-grid">
|
||||||
|
<div class="color-item">
|
||||||
|
<label>Primary:</label>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="masterPrimaryColor"
|
||||||
|
value="#4285f4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<label>Secondary:</label>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="masterSecondaryColor"
|
||||||
|
value="#34a853"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<label>Accent:</label>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="masterAccentColor"
|
||||||
|
value="#fbbc04"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<label>Background:</label>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="masterBgColor"
|
||||||
|
value="#ffffff"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<label>Text:</label>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="masterTextColor"
|
||||||
|
value="#212121"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<label>Text Light:</label>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
id="masterTextLightColor"
|
||||||
|
value="#666666"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="master-section">
|
||||||
|
<h4>Theme Fonts</h4>
|
||||||
|
<div class="font-grid">
|
||||||
|
<div class="font-item">
|
||||||
|
<label>Heading Font:</label>
|
||||||
|
<select id="masterHeadingFont">
|
||||||
|
<option value="Arial">Arial</option>
|
||||||
|
<option value="Helvetica">Helvetica</option>
|
||||||
|
<option value="Georgia">Georgia</option>
|
||||||
|
<option value="Times New Roman">
|
||||||
|
Times New Roman
|
||||||
|
</option>
|
||||||
|
<option value="Verdana">Verdana</option>
|
||||||
|
<option value="Trebuchet MS">
|
||||||
|
Trebuchet MS
|
||||||
|
</option>
|
||||||
|
<option value="Impact">Impact</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="font-item">
|
||||||
|
<label>Body Font:</label>
|
||||||
|
<select id="masterBodyFont">
|
||||||
|
<option value="Arial">Arial</option>
|
||||||
|
<option value="Helvetica">Helvetica</option>
|
||||||
|
<option value="Georgia">Georgia</option>
|
||||||
|
<option value="Times New Roman">
|
||||||
|
Times New Roman
|
||||||
|
</option>
|
||||||
|
<option value="Verdana">Verdana</option>
|
||||||
|
<option value="Calibri">Calibri</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="master-section">
|
||||||
|
<h4>Preview</h4>
|
||||||
|
<div class="master-preview" id="masterPreview">
|
||||||
|
<div class="preview-slide">
|
||||||
|
<h2 class="preview-heading" id="previewHeading">
|
||||||
|
Title Text
|
||||||
|
</h2>
|
||||||
|
<p class="preview-body" id="previewBody">
|
||||||
|
Body text example
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-actions">
|
||||||
|
<button class="btn-secondary" id="resetMasterBtn">
|
||||||
|
Reset to Default
|
||||||
|
</button>
|
||||||
|
<button class="btn-secondary" id="cancelMasterBtn">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button class="btn-primary" id="applyMasterBtn">
|
||||||
|
Apply to All Slides
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="slides/slides.js"></script>
|
<script src="slides/slides.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,123 @@
|
||||||
.getElementById("shareBtn")
|
.getElementById("shareBtn")
|
||||||
?.addEventListener("click", () => showModal("shareModal"));
|
?.addEventListener("click", () => showModal("shareModal"));
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("transitionsBtn")
|
||||||
|
?.addEventListener("click", showTransitionsModal);
|
||||||
|
document
|
||||||
|
.getElementById("closeTransitionsModal")
|
||||||
|
?.addEventListener("click", () => hideModal("transitionsModal"));
|
||||||
|
document
|
||||||
|
.getElementById("applyTransitionsBtn")
|
||||||
|
?.addEventListener("click", applyTransition);
|
||||||
|
document
|
||||||
|
.getElementById("cancelTransitionsBtn")
|
||||||
|
?.addEventListener("click", () => hideModal("transitionsModal"));
|
||||||
|
document
|
||||||
|
.getElementById("transitionDuration")
|
||||||
|
?.addEventListener("input", updateDurationDisplay);
|
||||||
|
document.querySelectorAll(".transition-btn").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () =>
|
||||||
|
selectTransition(btn.dataset.transition),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("animationsBtn")
|
||||||
|
?.addEventListener("click", showAnimationsModal);
|
||||||
|
document
|
||||||
|
.getElementById("closeAnimationsModal")
|
||||||
|
?.addEventListener("click", () => hideModal("animationsModal"));
|
||||||
|
document
|
||||||
|
.getElementById("applyAnimationsBtn")
|
||||||
|
?.addEventListener("click", applyAnimation);
|
||||||
|
document
|
||||||
|
.getElementById("cancelAnimationsBtn")
|
||||||
|
?.addEventListener("click", () => hideModal("animationsModal"));
|
||||||
|
document
|
||||||
|
.getElementById("previewAnimationBtn")
|
||||||
|
?.addEventListener("click", previewAnimation);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("slideSorterBtn")
|
||||||
|
?.addEventListener("click", showSlideSorter);
|
||||||
|
document
|
||||||
|
.getElementById("closeSlideSorterModal")
|
||||||
|
?.addEventListener("click", () => hideModal("slideSorterModal"));
|
||||||
|
document
|
||||||
|
.getElementById("applySorterBtn")
|
||||||
|
?.addEventListener("click", applySorterChanges);
|
||||||
|
document
|
||||||
|
.getElementById("cancelSorterBtn")
|
||||||
|
?.addEventListener("click", () => hideModal("slideSorterModal"));
|
||||||
|
document
|
||||||
|
.getElementById("sorterAddSlide")
|
||||||
|
?.addEventListener("click", sorterAddSlide);
|
||||||
|
document
|
||||||
|
.getElementById("sorterDuplicateSlide")
|
||||||
|
?.addEventListener("click", sorterDuplicateSlide);
|
||||||
|
document
|
||||||
|
.getElementById("sorterDeleteSlide")
|
||||||
|
?.addEventListener("click", sorterDeleteSlide);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("masterSlideBtn")
|
||||||
|
?.addEventListener("click", showMasterSlideModal);
|
||||||
|
document
|
||||||
|
.getElementById("closeMasterSlideModal")
|
||||||
|
?.addEventListener("click", () => hideModal("masterSlideModal"));
|
||||||
|
document
|
||||||
|
.getElementById("applyMasterBtn")
|
||||||
|
?.addEventListener("click", applyMasterSlide);
|
||||||
|
document
|
||||||
|
.getElementById("cancelMasterBtn")
|
||||||
|
?.addEventListener("click", () => hideModal("masterSlideModal"));
|
||||||
|
document
|
||||||
|
.getElementById("resetMasterBtn")
|
||||||
|
?.addEventListener("click", resetMasterSlide);
|
||||||
|
document.querySelectorAll(".master-layout-item").forEach((item) => {
|
||||||
|
item.addEventListener("click", () =>
|
||||||
|
selectMasterLayout(item.dataset.layout),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById("masterPrimaryColor")
|
||||||
|
?.addEventListener("input", updateMasterPreview);
|
||||||
|
document
|
||||||
|
.getElementById("masterSecondaryColor")
|
||||||
|
?.addEventListener("input", updateMasterPreview);
|
||||||
|
document
|
||||||
|
.getElementById("masterAccentColor")
|
||||||
|
?.addEventListener("input", updateMasterPreview);
|
||||||
|
document
|
||||||
|
.getElementById("masterBgColor")
|
||||||
|
?.addEventListener("input", updateMasterPreview);
|
||||||
|
document
|
||||||
|
.getElementById("masterTextColor")
|
||||||
|
?.addEventListener("input", updateMasterPreview);
|
||||||
|
document
|
||||||
|
.getElementById("masterTextLightColor")
|
||||||
|
?.addEventListener("input", updateMasterPreview);
|
||||||
|
document
|
||||||
|
.getElementById("masterHeadingFont")
|
||||||
|
?.addEventListener("change", updateMasterPreview);
|
||||||
|
document
|
||||||
|
.getElementById("masterBodyFont")
|
||||||
|
?.addEventListener("change", updateMasterPreview);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("exportPdfBtn")
|
||||||
|
?.addEventListener("click", showExportPdfModal);
|
||||||
|
document
|
||||||
|
.getElementById("closeExportPdfModal")
|
||||||
|
?.addEventListener("click", () => hideModal("exportPdfModal"));
|
||||||
|
document
|
||||||
|
.getElementById("exportPdfBtnConfirm")
|
||||||
|
?.addEventListener("click", exportToPdf);
|
||||||
|
document
|
||||||
|
.getElementById("cancelExportPdfBtn")
|
||||||
|
?.addEventListener("click", () => hideModal("exportPdfModal"));
|
||||||
|
|
||||||
document.getElementById("zoomInBtn")?.addEventListener("click", zoomIn);
|
document.getElementById("zoomInBtn")?.addEventListener("click", zoomIn);
|
||||||
document.getElementById("zoomOutBtn")?.addEventListener("click", zoomOut);
|
document.getElementById("zoomOutBtn")?.addEventListener("click", zoomOut);
|
||||||
|
|
||||||
|
|
@ -2008,6 +2125,780 @@
|
||||||
return localStorage.getItem("gb-user-name") || "Anonymous";
|
return localStorage.getItem("gb-user-name") || "Anonymous";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showTransitionsModal() {
|
||||||
|
showModal("transitionsModal");
|
||||||
|
const currentSlide = state.slides[state.currentSlideIndex];
|
||||||
|
if (currentSlide?.transition?.transition_type) {
|
||||||
|
selectTransition(currentSlide.transition.transition_type);
|
||||||
|
}
|
||||||
|
if (currentSlide?.transition?.duration) {
|
||||||
|
const durationInput = document.getElementById("transitionDuration");
|
||||||
|
const durationValue = document.getElementById("durationValue");
|
||||||
|
if (durationInput) durationInput.value = currentSlide.transition.duration;
|
||||||
|
if (durationValue)
|
||||||
|
durationValue.textContent = `${currentSlide.transition.duration}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectTransition(transitionType) {
|
||||||
|
document.querySelectorAll(".transition-btn").forEach((btn) => {
|
||||||
|
btn.classList.toggle("active", btn.dataset.transition === transitionType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDurationDisplay() {
|
||||||
|
const durationInput = document.getElementById("transitionDuration");
|
||||||
|
const durationValue = document.getElementById("durationValue");
|
||||||
|
if (durationInput && durationValue) {
|
||||||
|
durationValue.textContent = `${durationInput.value}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTransition() {
|
||||||
|
const activeBtn = document.querySelector(".transition-btn.active");
|
||||||
|
const transitionType = activeBtn?.dataset.transition || "none";
|
||||||
|
const duration = parseFloat(
|
||||||
|
document.getElementById("transitionDuration")?.value || 0.5,
|
||||||
|
);
|
||||||
|
const applyToAll = document.getElementById("applyToAllSlides")?.checked;
|
||||||
|
|
||||||
|
saveToHistory();
|
||||||
|
|
||||||
|
const transition = {
|
||||||
|
transition_type: transitionType,
|
||||||
|
duration: duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (applyToAll) {
|
||||||
|
state.slides.forEach((slide) => {
|
||||||
|
slide.transition = { ...transition };
|
||||||
|
});
|
||||||
|
addChatMessage(
|
||||||
|
"assistant",
|
||||||
|
`Applied ${transitionType} transition to all slides.`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const currentSlide = state.slides[state.currentSlideIndex];
|
||||||
|
if (currentSlide) {
|
||||||
|
currentSlide.transition = transition;
|
||||||
|
}
|
||||||
|
addChatMessage(
|
||||||
|
"assistant",
|
||||||
|
`Applied ${transitionType} transition to current slide.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideModal("transitionsModal");
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAnimationsModal() {
|
||||||
|
showModal("animationsModal");
|
||||||
|
updateSelectedElementInfo();
|
||||||
|
updateAnimationOrderList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelectedElementInfo() {
|
||||||
|
const infoEl = document.getElementById("selectedElementInfo");
|
||||||
|
if (!infoEl) return;
|
||||||
|
|
||||||
|
if (state.selectedElement) {
|
||||||
|
const slide = state.slides[state.currentSlideIndex];
|
||||||
|
const element = slide?.elements?.find(
|
||||||
|
(el) => el.id === state.selectedElement,
|
||||||
|
);
|
||||||
|
if (element) {
|
||||||
|
const type = element.element_type || "Unknown";
|
||||||
|
const content =
|
||||||
|
element.content?.text?.substring(0, 30) ||
|
||||||
|
element.content?.shape_type ||
|
||||||
|
"";
|
||||||
|
infoEl.textContent = `${type}: ${content}${content.length > 30 ? "..." : ""}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
infoEl.textContent = "No element selected";
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAnimationOrderList() {
|
||||||
|
const listEl = document.getElementById("animationOrderList");
|
||||||
|
if (!listEl) return;
|
||||||
|
|
||||||
|
const slide = state.slides[state.currentSlideIndex];
|
||||||
|
const animations = [];
|
||||||
|
|
||||||
|
slide?.elements?.forEach((element) => {
|
||||||
|
if (element.animations?.length > 0) {
|
||||||
|
element.animations.forEach((anim) => {
|
||||||
|
animations.push({
|
||||||
|
elementId: element.id,
|
||||||
|
elementType: element.element_type,
|
||||||
|
animation: anim,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (animations.length === 0) {
|
||||||
|
listEl.innerHTML = '<p class="no-animations">No animations added yet</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listEl.innerHTML = animations
|
||||||
|
.map(
|
||||||
|
(item, index) => `
|
||||||
|
<div class="animation-item" data-index="${index}">
|
||||||
|
<div>
|
||||||
|
<div class="animation-name">${item.animation.type || "Animation"}</div>
|
||||||
|
<div class="animation-element">${item.elementType}</div>
|
||||||
|
</div>
|
||||||
|
<button class="animation-remove" data-element="${item.elementId}">×</button>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
listEl.querySelectorAll(".animation-remove").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => removeAnimation(btn.dataset.element));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAnimation() {
|
||||||
|
if (!state.selectedElement) {
|
||||||
|
addChatMessage(
|
||||||
|
"assistant",
|
||||||
|
"Please select an element on the slide first.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entrance = document.getElementById("entranceAnimation")?.value;
|
||||||
|
const emphasis = document.getElementById("emphasisAnimation")?.value;
|
||||||
|
const exit = document.getElementById("exitAnimation")?.value;
|
||||||
|
const start =
|
||||||
|
document.getElementById("animationStart")?.value || "on-click";
|
||||||
|
const duration = parseFloat(
|
||||||
|
document.getElementById("animationDuration")?.value || 0.5,
|
||||||
|
);
|
||||||
|
const delay = parseFloat(
|
||||||
|
document.getElementById("animationDelay")?.value || 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const slide = state.slides[state.currentSlideIndex];
|
||||||
|
const element = slide?.elements?.find(
|
||||||
|
(el) => el.id === state.selectedElement,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
saveToHistory();
|
||||||
|
|
||||||
|
element.animations = [];
|
||||||
|
|
||||||
|
if (entrance && entrance !== "none") {
|
||||||
|
element.animations.push({
|
||||||
|
type: entrance,
|
||||||
|
category: "entrance",
|
||||||
|
start,
|
||||||
|
duration,
|
||||||
|
delay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emphasis && emphasis !== "none") {
|
||||||
|
element.animations.push({
|
||||||
|
type: emphasis,
|
||||||
|
category: "emphasis",
|
||||||
|
start: "after-previous",
|
||||||
|
duration,
|
||||||
|
delay: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exit && exit !== "none") {
|
||||||
|
element.animations.push({
|
||||||
|
type: exit,
|
||||||
|
category: "exit",
|
||||||
|
start: "after-previous",
|
||||||
|
duration,
|
||||||
|
delay: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAnimationOrderList();
|
||||||
|
hideModal("animationsModal");
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
addChatMessage("assistant", "Animation applied to selected element.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAnimation(elementId) {
|
||||||
|
const slide = state.slides[state.currentSlideIndex];
|
||||||
|
const element = slide?.elements?.find((el) => el.id === elementId);
|
||||||
|
if (element) {
|
||||||
|
element.animations = [];
|
||||||
|
updateAnimationOrderList();
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function previewAnimation() {
|
||||||
|
if (!state.selectedElement) {
|
||||||
|
addChatMessage(
|
||||||
|
"assistant",
|
||||||
|
"Select an element to preview its animation.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entrance = document.getElementById("entranceAnimation")?.value;
|
||||||
|
const node = document.querySelector(
|
||||||
|
`[data-element-id="${state.selectedElement}"]`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!node || !entrance || entrance === "none") return;
|
||||||
|
|
||||||
|
node.style.animation = "none";
|
||||||
|
node.offsetHeight;
|
||||||
|
|
||||||
|
const animationName = entrance.replace(/-/g, "");
|
||||||
|
node.style.animation = `${animationName} 0.5s ease`;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
node.style.animation = "";
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorterSlideOrder = [];
|
||||||
|
let sorterSelectedSlide = null;
|
||||||
|
|
||||||
|
function showSlideSorter() {
|
||||||
|
showModal("slideSorterModal");
|
||||||
|
sorterSlideOrder = state.slides.map((_, i) => i);
|
||||||
|
sorterSelectedSlide = null;
|
||||||
|
renderSorterGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSorterGrid() {
|
||||||
|
const grid = document.getElementById("sorterGrid");
|
||||||
|
if (!grid) return;
|
||||||
|
|
||||||
|
grid.innerHTML = sorterSlideOrder
|
||||||
|
.map((slideIndex, position) => {
|
||||||
|
const slide = state.slides[slideIndex];
|
||||||
|
if (!slide) return "";
|
||||||
|
|
||||||
|
const isSelected = sorterSelectedSlide === position;
|
||||||
|
return `
|
||||||
|
<div class="sorter-slide ${isSelected ? "selected" : ""}"
|
||||||
|
data-position="${position}"
|
||||||
|
data-slide-index="${slideIndex}"
|
||||||
|
draggable="true">
|
||||||
|
<div class="sorter-slide-content">
|
||||||
|
${renderSorterSlidePreview(slide)}
|
||||||
|
</div>
|
||||||
|
<div class="sorter-slide-number">${position + 1}</div>
|
||||||
|
<div class="sorter-slide-actions">
|
||||||
|
<button data-action="duplicate" title="Duplicate">⎘</button>
|
||||||
|
<button data-action="delete" title="Delete">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
grid.querySelectorAll(".sorter-slide").forEach((el) => {
|
||||||
|
el.addEventListener("click", (e) => {
|
||||||
|
if (e.target.closest(".sorter-slide-actions")) return;
|
||||||
|
sorterSelectSlide(parseInt(el.dataset.position));
|
||||||
|
});
|
||||||
|
|
||||||
|
el.addEventListener("dragstart", handleSorterDragStart);
|
||||||
|
el.addEventListener("dragover", handleSorterDragOver);
|
||||||
|
el.addEventListener("drop", handleSorterDrop);
|
||||||
|
el.addEventListener("dragend", handleSorterDragEnd);
|
||||||
|
|
||||||
|
el.querySelectorAll(".sorter-slide-actions button").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const action = btn.dataset.action;
|
||||||
|
const position = parseInt(el.dataset.position);
|
||||||
|
if (action === "duplicate") {
|
||||||
|
sorterDuplicateAt(position);
|
||||||
|
} else if (action === "delete") {
|
||||||
|
sorterDeleteAt(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSorterSlidePreview(slide) {
|
||||||
|
const bgColor = slide.background?.color || "#ffffff";
|
||||||
|
let html = `<div style="width:100%;height:100%;background:${bgColor};padding:8px;font-size:6px;">`;
|
||||||
|
|
||||||
|
if (slide.elements) {
|
||||||
|
slide.elements.slice(0, 3).forEach((el) => {
|
||||||
|
if (el.element_type === "text" && el.content?.text) {
|
||||||
|
const text = el.content.text.substring(0, 50);
|
||||||
|
html += `<div style="margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(text)}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
html += "</div>";
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sorterSelectSlide(position) {
|
||||||
|
sorterSelectedSlide = position;
|
||||||
|
document.querySelectorAll(".sorter-slide").forEach((el) => {
|
||||||
|
el.classList.toggle(
|
||||||
|
"selected",
|
||||||
|
parseInt(el.dataset.position) === position,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let draggedPosition = null;
|
||||||
|
|
||||||
|
function handleSorterDragStart(e) {
|
||||||
|
draggedPosition = parseInt(e.currentTarget.dataset.position);
|
||||||
|
e.currentTarget.classList.add("dragging");
|
||||||
|
e.dataTransfer.effectAllowed = "move";
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSorterDragOver(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "move";
|
||||||
|
e.currentTarget.classList.add("drag-over");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSorterDrop(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const targetPosition = parseInt(e.currentTarget.dataset.position);
|
||||||
|
|
||||||
|
if (draggedPosition !== null && draggedPosition !== targetPosition) {
|
||||||
|
const draggedIndex = sorterSlideOrder[draggedPosition];
|
||||||
|
sorterSlideOrder.splice(draggedPosition, 1);
|
||||||
|
sorterSlideOrder.splice(targetPosition, 0, draggedIndex);
|
||||||
|
renderSorterGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
e.currentTarget.classList.remove("drag-over");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSorterDragEnd(e) {
|
||||||
|
e.currentTarget.classList.remove("dragging");
|
||||||
|
document.querySelectorAll(".sorter-slide").forEach((el) => {
|
||||||
|
el.classList.remove("drag-over");
|
||||||
|
});
|
||||||
|
draggedPosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sorterAddSlide() {
|
||||||
|
const newSlide = createSlide("blank");
|
||||||
|
state.slides.push(newSlide);
|
||||||
|
sorterSlideOrder.push(state.slides.length - 1);
|
||||||
|
renderSorterGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sorterDuplicateSlide() {
|
||||||
|
if (sorterSelectedSlide === null) {
|
||||||
|
addChatMessage("assistant", "Select a slide to duplicate.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sorterDuplicateAt(sorterSelectedSlide);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sorterDuplicateAt(position) {
|
||||||
|
const originalIndex = sorterSlideOrder[position];
|
||||||
|
const original = state.slides[originalIndex];
|
||||||
|
if (!original) return;
|
||||||
|
|
||||||
|
const duplicated = JSON.parse(JSON.stringify(original));
|
||||||
|
duplicated.id = generateId();
|
||||||
|
state.slides.push(duplicated);
|
||||||
|
sorterSlideOrder.splice(position + 1, 0, state.slides.length - 1);
|
||||||
|
renderSorterGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sorterDeleteSlide() {
|
||||||
|
if (sorterSelectedSlide === null) {
|
||||||
|
addChatMessage("assistant", "Select a slide to delete.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sorterDeleteAt(sorterSelectedSlide);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sorterDeleteAt(position) {
|
||||||
|
if (sorterSlideOrder.length <= 1) {
|
||||||
|
addChatMessage("assistant", "Cannot delete the last slide.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sorterSlideOrder.splice(position, 1);
|
||||||
|
if (sorterSelectedSlide >= sorterSlideOrder.length) {
|
||||||
|
sorterSelectedSlide = sorterSlideOrder.length - 1;
|
||||||
|
}
|
||||||
|
renderSorterGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySorterChanges() {
|
||||||
|
const reorderedSlides = sorterSlideOrder.map((i) => state.slides[i]);
|
||||||
|
state.slides = reorderedSlides;
|
||||||
|
state.currentSlideIndex = 0;
|
||||||
|
|
||||||
|
hideModal("slideSorterModal");
|
||||||
|
renderThumbnails();
|
||||||
|
renderCurrentSlide();
|
||||||
|
updateSlideCounter();
|
||||||
|
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
addChatMessage("assistant", "Slide order updated!");
|
||||||
|
}
|
||||||
|
|
||||||
|
function showExportPdfModal() {
|
||||||
|
showModal("exportPdfModal");
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportToPdf() {
|
||||||
|
const rangeType = document.querySelector(
|
||||||
|
'input[name="slideRange"]:checked',
|
||||||
|
)?.value;
|
||||||
|
const layout = document.getElementById("pdfLayout")?.value || "full";
|
||||||
|
const orientation =
|
||||||
|
document.getElementById("pdfOrientation")?.value || "landscape";
|
||||||
|
|
||||||
|
let slidesToExport = [];
|
||||||
|
|
||||||
|
switch (rangeType) {
|
||||||
|
case "all":
|
||||||
|
slidesToExport = state.slides.map((_, i) => i);
|
||||||
|
break;
|
||||||
|
case "current":
|
||||||
|
slidesToExport = [state.currentSlideIndex];
|
||||||
|
break;
|
||||||
|
case "custom":
|
||||||
|
const customRange = document.getElementById("customRange")?.value || "";
|
||||||
|
slidesToExport = parseSlideRange(customRange);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
slidesToExport = state.slides.map((_, i) => i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slidesToExport.length === 0) {
|
||||||
|
addChatMessage("assistant", "No slides to export.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const printWindow = window.open("", "_blank");
|
||||||
|
const slidesPerPage = getLayoutSlidesPerPage(layout);
|
||||||
|
|
||||||
|
let htmlContent = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>${state.presentationName} - PDF Export</title>
|
||||||
|
<style>
|
||||||
|
@page { size: ${orientation}; margin: 0.5in; }
|
||||||
|
@media print {
|
||||||
|
.page-break { page-break-after: always; }
|
||||||
|
}
|
||||||
|
body { font-family: Arial, sans-serif; margin: 0; padding: 0; }
|
||||||
|
.slide-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.slide {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.slide-full { width: 100%; aspect-ratio: 16/9; }
|
||||||
|
.slide-2 { width: 45%; aspect-ratio: 16/9; }
|
||||||
|
.slide-4 { width: 45%; aspect-ratio: 16/9; }
|
||||||
|
.slide-6 { width: 30%; aspect-ratio: 16/9; }
|
||||||
|
.slide-content { padding: 20px; height: 100%; box-sizing: border-box; }
|
||||||
|
.slide-number { text-align: center; font-size: 12px; color: #666; margin-top: 8px; }
|
||||||
|
.notes-section { padding: 10px; font-size: 11px; border-top: 1px solid #ccc; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
`;
|
||||||
|
|
||||||
|
let slideCount = 0;
|
||||||
|
slidesToExport.forEach((slideIndex, i) => {
|
||||||
|
const slide = state.slides[slideIndex];
|
||||||
|
if (!slide) return;
|
||||||
|
|
||||||
|
if (slideCount > 0 && slideCount % slidesPerPage === 0) {
|
||||||
|
htmlContent += '<div class="page-break"></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slideCount % slidesPerPage === 0) {
|
||||||
|
htmlContent += '<div class="slide-container">';
|
||||||
|
}
|
||||||
|
|
||||||
|
const slideClass =
|
||||||
|
slidesPerPage === 1
|
||||||
|
? "slide-full"
|
||||||
|
: slidesPerPage === 2
|
||||||
|
? "slide-2"
|
||||||
|
: slidesPerPage === 4
|
||||||
|
? "slide-4"
|
||||||
|
: "slide-6";
|
||||||
|
const bgColor = slide.background?.color || "#ffffff";
|
||||||
|
|
||||||
|
htmlContent += `
|
||||||
|
<div class="slide ${slideClass}" style="background:${bgColor};">
|
||||||
|
<div class="slide-content">
|
||||||
|
${renderSlideContentForExport(slide)}
|
||||||
|
</div>
|
||||||
|
<div class="slide-number">Slide ${slideIndex + 1}</div>
|
||||||
|
${layout === "notes" && slide.notes ? `<div class="notes-section">${escapeHtml(slide.notes)}</div>` : ""}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
slideCount++;
|
||||||
|
if (slideCount % slidesPerPage === 0 || i === slidesToExport.length - 1) {
|
||||||
|
htmlContent += "</div>";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
htmlContent += "</body></html>";
|
||||||
|
|
||||||
|
printWindow.document.write(htmlContent);
|
||||||
|
printWindow.document.close();
|
||||||
|
printWindow.focus();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
printWindow.print();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
hideModal("exportPdfModal");
|
||||||
|
addChatMessage(
|
||||||
|
"assistant",
|
||||||
|
`Exporting ${slidesToExport.length} slide(s) to PDF...`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSlideRange(rangeStr) {
|
||||||
|
const slides = [];
|
||||||
|
const parts = rangeStr.split(",");
|
||||||
|
|
||||||
|
parts.forEach((part) => {
|
||||||
|
part = part.trim();
|
||||||
|
if (part.includes("-")) {
|
||||||
|
const [start, end] = part.split("-").map((n) => parseInt(n.trim()) - 1);
|
||||||
|
for (
|
||||||
|
let i = Math.max(0, start);
|
||||||
|
i <= Math.min(state.slides.length - 1, end);
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
if (!slides.includes(i)) slides.push(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const num = parseInt(part) - 1;
|
||||||
|
if (num >= 0 && num < state.slides.length && !slides.includes(num)) {
|
||||||
|
slides.push(num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return slides.sort((a, b) => a - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLayoutSlidesPerPage(layout) {
|
||||||
|
switch (layout) {
|
||||||
|
case "full":
|
||||||
|
case "notes":
|
||||||
|
return 1;
|
||||||
|
case "handout-2":
|
||||||
|
return 2;
|
||||||
|
case "handout-4":
|
||||||
|
return 4;
|
||||||
|
case "handout-6":
|
||||||
|
return 6;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSlideContentForExport(slide) {
|
||||||
|
let html = "";
|
||||||
|
if (slide.elements) {
|
||||||
|
slide.elements.forEach((el) => {
|
||||||
|
if (el.element_type === "text" && el.content?.text) {
|
||||||
|
const fontSize = el.style?.fontSize || 16;
|
||||||
|
const fontWeight = el.style?.fontWeight || "normal";
|
||||||
|
const color = el.style?.color || "#000";
|
||||||
|
html += `<div style="font-size:${fontSize}px;font-weight:${fontWeight};color:${color};margin-bottom:8px;">${escapeHtml(el.content.text)}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return html || "<p>Empty slide</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedMasterLayout = "title";
|
||||||
|
|
||||||
|
function showMasterSlideModal() {
|
||||||
|
showModal("masterSlideModal");
|
||||||
|
selectedMasterLayout = "title";
|
||||||
|
|
||||||
|
if (state.theme) {
|
||||||
|
const colors = state.theme.colors || {};
|
||||||
|
const fonts = state.theme.fonts || {};
|
||||||
|
|
||||||
|
setColorInput("masterPrimaryColor", colors.primary || "#4285f4");
|
||||||
|
setColorInput("masterSecondaryColor", colors.secondary || "#34a853");
|
||||||
|
setColorInput("masterAccentColor", colors.accent || "#fbbc04");
|
||||||
|
setColorInput("masterBgColor", colors.background || "#ffffff");
|
||||||
|
setColorInput("masterTextColor", colors.text || "#212121");
|
||||||
|
setColorInput("masterTextLightColor", colors.text_light || "#666666");
|
||||||
|
|
||||||
|
setSelectValue("masterHeadingFont", fonts.heading || "Arial");
|
||||||
|
setSelectValue("masterBodyFont", fonts.body || "Arial");
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMasterPreview();
|
||||||
|
updateMasterLayoutSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setColorInput(id, value) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelectValue(id, value) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectMasterLayout(layout) {
|
||||||
|
selectedMasterLayout = layout;
|
||||||
|
updateMasterLayoutSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMasterLayoutSelection() {
|
||||||
|
document.querySelectorAll(".master-layout-item").forEach((item) => {
|
||||||
|
item.classList.toggle(
|
||||||
|
"active",
|
||||||
|
item.dataset.layout === selectedMasterLayout,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMasterPreview() {
|
||||||
|
const bgColor =
|
||||||
|
document.getElementById("masterBgColor")?.value || "#ffffff";
|
||||||
|
const textColor =
|
||||||
|
document.getElementById("masterTextColor")?.value || "#212121";
|
||||||
|
const textLightColor =
|
||||||
|
document.getElementById("masterTextLightColor")?.value || "#666666";
|
||||||
|
const headingFont =
|
||||||
|
document.getElementById("masterHeadingFont")?.value || "Arial";
|
||||||
|
const bodyFont =
|
||||||
|
document.getElementById("masterBodyFont")?.value || "Arial";
|
||||||
|
|
||||||
|
const previewSlide = document.querySelector(".preview-slide");
|
||||||
|
const previewHeading = document.getElementById("previewHeading");
|
||||||
|
const previewBody = document.getElementById("previewBody");
|
||||||
|
|
||||||
|
if (previewSlide) {
|
||||||
|
previewSlide.style.background = bgColor;
|
||||||
|
}
|
||||||
|
if (previewHeading) {
|
||||||
|
previewHeading.style.color = textColor;
|
||||||
|
previewHeading.style.fontFamily = headingFont;
|
||||||
|
}
|
||||||
|
if (previewBody) {
|
||||||
|
previewBody.style.color = textLightColor;
|
||||||
|
previewBody.style.fontFamily = bodyFont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMasterSlide() {
|
||||||
|
const primaryColor =
|
||||||
|
document.getElementById("masterPrimaryColor")?.value || "#4285f4";
|
||||||
|
const secondaryColor =
|
||||||
|
document.getElementById("masterSecondaryColor")?.value || "#34a853";
|
||||||
|
const accentColor =
|
||||||
|
document.getElementById("masterAccentColor")?.value || "#fbbc04";
|
||||||
|
const bgColor =
|
||||||
|
document.getElementById("masterBgColor")?.value || "#ffffff";
|
||||||
|
const textColor =
|
||||||
|
document.getElementById("masterTextColor")?.value || "#212121";
|
||||||
|
const textLightColor =
|
||||||
|
document.getElementById("masterTextLightColor")?.value || "#666666";
|
||||||
|
const headingFont =
|
||||||
|
document.getElementById("masterHeadingFont")?.value || "Arial";
|
||||||
|
const bodyFont =
|
||||||
|
document.getElementById("masterBodyFont")?.value || "Arial";
|
||||||
|
|
||||||
|
saveToHistory();
|
||||||
|
|
||||||
|
state.theme = {
|
||||||
|
name: "Custom",
|
||||||
|
colors: {
|
||||||
|
primary: primaryColor,
|
||||||
|
secondary: secondaryColor,
|
||||||
|
accent: accentColor,
|
||||||
|
background: bgColor,
|
||||||
|
text: textColor,
|
||||||
|
text_light: textLightColor,
|
||||||
|
},
|
||||||
|
fonts: {
|
||||||
|
heading: headingFont,
|
||||||
|
body: bodyFont,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
state.slides.forEach((slide) => {
|
||||||
|
slide.background = slide.background || {};
|
||||||
|
slide.background.color = bgColor;
|
||||||
|
|
||||||
|
if (slide.elements) {
|
||||||
|
slide.elements.forEach((el) => {
|
||||||
|
if (el.element_type === "text") {
|
||||||
|
el.style = el.style || {};
|
||||||
|
const isHeading =
|
||||||
|
el.style.fontSize >= 24 || el.style.fontWeight === "bold";
|
||||||
|
el.style.fontFamily = isHeading ? headingFont : bodyFont;
|
||||||
|
el.style.color = isHeading ? textColor : textLightColor;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
hideModal("masterSlideModal");
|
||||||
|
renderThumbnails();
|
||||||
|
renderCurrentSlide();
|
||||||
|
|
||||||
|
state.isDirty = true;
|
||||||
|
scheduleAutoSave();
|
||||||
|
addChatMessage("assistant", "Master slide theme applied to all slides!");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetMasterSlide() {
|
||||||
|
setColorInput("masterPrimaryColor", "#4285f4");
|
||||||
|
setColorInput("masterSecondaryColor", "#34a853");
|
||||||
|
setColorInput("masterAccentColor", "#fbbc04");
|
||||||
|
setColorInput("masterBgColor", "#ffffff");
|
||||||
|
setColorInput("masterTextColor", "#212121");
|
||||||
|
setColorInput("masterTextLightColor", "#666666");
|
||||||
|
setSelectValue("masterHeadingFont", "Arial");
|
||||||
|
setSelectValue("masterBodyFont", "Arial");
|
||||||
|
|
||||||
|
updateMasterPreview();
|
||||||
|
}
|
||||||
|
|
||||||
window.gbSlides = {
|
window.gbSlides = {
|
||||||
init,
|
init,
|
||||||
addSlide,
|
addSlide,
|
||||||
|
|
@ -2023,6 +2914,11 @@
|
||||||
hideModal,
|
hideModal,
|
||||||
toggleChatPanel,
|
toggleChatPanel,
|
||||||
savePresentation,
|
savePresentation,
|
||||||
|
showTransitionsModal,
|
||||||
|
showAnimationsModal,
|
||||||
|
showSlideSorter,
|
||||||
|
exportToPdf,
|
||||||
|
showMasterSlideModal,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue