diff --git a/src/features/qword/QWord.tsx b/src/features/qword/QWord.tsx index a805b4d..d58a835 100644 --- a/src/features/qword/QWord.tsx +++ b/src/features/qword/QWord.tsx @@ -1,9 +1,12 @@ import { useLayoutEffect, useMemo, useRef, useState } from "react"; import { AlignCenter, + AlignJustify, AlignLeft, AlignRight, Bold, + ClipboardPaste, + Copy, Download, FileText, FileUp, @@ -11,10 +14,17 @@ import { Italic, Link, List, + ListOrdered, PaintBucket, + RemoveFormatting, + Replace, Save, + Scissors, + Search, SeparatorHorizontal, Strikethrough, + Subscript, + Superscript, Type, Underline } from "lucide-react"; @@ -31,6 +41,11 @@ interface QWordProps { onChange: (document: WordDocument) => void; } +type PageSearchWindow = Window & + typeof globalThis & { + find?: (query: string) => boolean; + }; + function countWords(html: string) { const text = html.replace(/<[^>]+>/g, " ").replace(/ /g, " ").trim(); return text ? text.split(/\s+/).length : 0; @@ -94,6 +109,32 @@ const highlightSwatches = [ { color: "#ffffff", label: "Без подсветки" } ]; +const fontFamilyOptions = ["Calibri", "Aptos", "Arial", "Times New Roman", "Courier New"]; +const fontSizeOptions = [9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36]; +const fontSizeCommandMap: Record = { + 9: "1", + 10: "2", + 11: "2", + 12: "3", + 14: "4", + 16: "4", + 18: "5", + 20: "5", + 24: "6", + 28: "6", + 36: "7" +}; +const styleGalleryOptions = [ + { value: "p", label: "Обычный" }, + { value: "h1", label: "Заголовок 1" }, + { value: "h2", label: "Заголовок 2" }, + { value: "blockquote", label: "Цитата" } +]; + +function findInPage(query: string) { + return (window as PageSearchWindow).find?.(query) ?? false; +} + const defaultPageViewHeight = 1180; function pageHeightFromStyles(element: HTMLElement) { @@ -152,6 +193,45 @@ export function QWord({ document, onChange }: QWordProps) { updateHtml(); } + function applyFontFamily(fontFamily: string) { + if (fontFamily) { + runCommand("fontName", fontFamily); + } + } + + function applyFontSize(fontSize: string) { + const pointSize = Number(fontSize); + if (Number.isFinite(pointSize)) { + runCommand("fontSize", fontSizeCommandMap[pointSize] ?? "3"); + } + } + + function findText() { + const query = window.prompt("Найти текст"); + if (!query) { + return; + } + + editorRef.current?.focus(); + findInPage(query); + } + + function replaceText() { + const query = window.prompt("Найти текст для замены"); + if (!query) { + return; + } + + editorRef.current?.focus(); + if (!findInPage(query)) { + return; + } + + const replacement = window.prompt("Заменить на", "") ?? ""; + globalThis.document.execCommand("insertText", false, replacement); + updateHtml(); + } + function insertLink() { const href = normalizedLinkInput(window.prompt("Введите адрес ссылки")); if (!href) { @@ -274,7 +354,7 @@ export function QWord({ document, onChange }: QWordProps) { return (
-
+
void insertImage(event.target.files?.[0])} aria-label="Вставить изображение" /> - onChange({ ...document, title: event.target.value })} - /> -
- - - - - - - - - - - +
+
+
+ + + + +
+ Файл +
+ +
+ +
+ + + +
+ Буфер обмена +
+ +
+
+ + +
+
+ + + + + + +
+
+
+
+
+
+
+ Шрифт +
+ +
+
+ + +
+
+ + + + +
+ + Абзац +
+ +
+
+ {styleGalleryOptions.map((style) => ( + + ))} +
+ Стили +
+ +
+ + + Вставка +
+ +
+ + + + Страниц: {pageView.count} · Слов: {wordCount} + + Редактирование +
-
-
-
-
- - - - - - - Страниц: {pageView.count} · Слов: {wordCount} -