diff --git a/ui/suite/docs/docs.css b/ui/suite/docs/docs.css
index 179628e..1909ba0 100644
--- a/ui/suite/docs/docs.css
+++ b/ui/suite/docs/docs.css
@@ -1098,6 +1098,377 @@
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 {
background: rgba(66, 133, 244, 0.3);
}
diff --git a/ui/suite/docs/docs.html b/ui/suite/docs/docs.html
index f81c1bf..dd68ee5 100644
--- a/ui/suite/docs/docs.html
+++ b/ui/suite/docs/docs.html
@@ -12,6 +12,46 @@
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/suite/docs/docs.js b/ui/suite/docs/docs.js
index aecfb3c..896a97e 100644
--- a/ui/suite/docs/docs.js
+++ b/ui/suite/docs/docs.js
@@ -20,6 +20,8 @@
chatPanelOpen: true,
driveSource: null,
zoom: 100,
+ findMatches: [],
+ findMatchIndex: -1,
};
const elements = {};
@@ -54,6 +56,11 @@
elements.imageModal = document.getElementById("imageModal");
elements.tableModal = document.getElementById("tableModal");
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() {
@@ -207,6 +214,94 @@
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);
}
@@ -1007,6 +1102,421 @@ ${content}
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,
+ '
$1',
+ );
+ 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(`
+
+
+
+
${state.docTitle}
+
+
+
+ ${showHeaders ? `` : ""}
+ ${content}
+
+
+ `);
+
+ 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 = "
";
+ 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 =
+ '
[Page #]';
+ break;
+ case "date":
+ fieldContent = `
${new Date().toLocaleDateString()}`;
+ break;
+ case "title":
+ fieldContent = `
${state.docTitle}`;
+ 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() {
state.docId = null;
state.docTitle = "Untitled Document";
diff --git a/ui/suite/sheet/sheet.css b/ui/suite/sheet/sheet.css
index 170eddb..45d7611 100644
--- a/ui/suite/sheet/sheet.css
+++ b/ui/suite/sheet/sheet.css
@@ -1329,3 +1329,749 @@
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;
+}
diff --git a/ui/suite/sheet/sheet.html b/ui/suite/sheet/sheet.html
index 91f85b0..7a44c83 100644
--- a/ui/suite/sheet/sheet.html
+++ b/ui/suite/sheet/sheet.html
@@ -45,6 +45,46 @@