Improve QWord ribbon fidelity
This commit is contained in:
+266
-98
@@ -1,9 +1,12 @@
|
|||||||
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AlignCenter,
|
AlignCenter,
|
||||||
|
AlignJustify,
|
||||||
AlignLeft,
|
AlignLeft,
|
||||||
AlignRight,
|
AlignRight,
|
||||||
Bold,
|
Bold,
|
||||||
|
ClipboardPaste,
|
||||||
|
Copy,
|
||||||
Download,
|
Download,
|
||||||
FileText,
|
FileText,
|
||||||
FileUp,
|
FileUp,
|
||||||
@@ -11,10 +14,17 @@ import {
|
|||||||
Italic,
|
Italic,
|
||||||
Link,
|
Link,
|
||||||
List,
|
List,
|
||||||
|
ListOrdered,
|
||||||
PaintBucket,
|
PaintBucket,
|
||||||
|
RemoveFormatting,
|
||||||
|
Replace,
|
||||||
Save,
|
Save,
|
||||||
|
Scissors,
|
||||||
|
Search,
|
||||||
SeparatorHorizontal,
|
SeparatorHorizontal,
|
||||||
Strikethrough,
|
Strikethrough,
|
||||||
|
Subscript,
|
||||||
|
Superscript,
|
||||||
Type,
|
Type,
|
||||||
Underline
|
Underline
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -31,6 +41,11 @@ interface QWordProps {
|
|||||||
onChange: (document: WordDocument) => void;
|
onChange: (document: WordDocument) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PageSearchWindow = Window &
|
||||||
|
typeof globalThis & {
|
||||||
|
find?: (query: string) => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
function countWords(html: string) {
|
function countWords(html: string) {
|
||||||
const text = html.replace(/<[^>]+>/g, " ").replace(/ /g, " ").trim();
|
const text = html.replace(/<[^>]+>/g, " ").replace(/ /g, " ").trim();
|
||||||
return text ? text.split(/\s+/).length : 0;
|
return text ? text.split(/\s+/).length : 0;
|
||||||
@@ -94,6 +109,32 @@ const highlightSwatches = [
|
|||||||
{ color: "#ffffff", label: "Без подсветки" }
|
{ 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<number, string> = {
|
||||||
|
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;
|
const defaultPageViewHeight = 1180;
|
||||||
|
|
||||||
function pageHeightFromStyles(element: HTMLElement) {
|
function pageHeightFromStyles(element: HTMLElement) {
|
||||||
@@ -152,6 +193,45 @@ export function QWord({ document, onChange }: QWordProps) {
|
|||||||
updateHtml();
|
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() {
|
function insertLink() {
|
||||||
const href = normalizedLinkInput(window.prompt("Введите адрес ссылки"));
|
const href = normalizedLinkInput(window.prompt("Введите адрес ссылки"));
|
||||||
if (!href) {
|
if (!href) {
|
||||||
@@ -274,7 +354,7 @@ export function QWord({ document, onChange }: QWordProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="editor-layout qword-layout">
|
<div className="editor-layout qword-layout">
|
||||||
<section className="document-stage" aria-label="Редактор QWord">
|
<section className="document-stage" aria-label="Редактор QWord">
|
||||||
<div className="module-toolbar">
|
<div className="module-toolbar word-module-toolbar">
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
className="hidden-file-input"
|
className="hidden-file-input"
|
||||||
@@ -291,104 +371,192 @@ export function QWord({ document, onChange }: QWordProps) {
|
|||||||
onChange={(event) => void insertImage(event.target.files?.[0])}
|
onChange={(event) => void insertImage(event.target.files?.[0])}
|
||||||
aria-label="Вставить изображение"
|
aria-label="Вставить изображение"
|
||||||
/>
|
/>
|
||||||
<input
|
<div className="word-ribbon" aria-label="Лента команд QWord">
|
||||||
className="file-title-input"
|
<section className="word-ribbon-group word-file-group" aria-label="Файл">
|
||||||
value={document.title}
|
<div className="word-file-actions">
|
||||||
aria-label="Название документа"
|
<button className="word-icon-command" type="button" onClick={() => void chooseDocx()} title="Открыть" aria-label="Открыть">
|
||||||
onChange={(event) => onChange({ ...document, title: event.target.value })}
|
<FileUp size={16} />
|
||||||
/>
|
</button>
|
||||||
<div className="segmented-tools" aria-label="Форматирование текста">
|
<button className="word-icon-command" type="button" onClick={() => void saveDocx()} title="Сохранить" aria-label="Сохранить">
|
||||||
<button type="button" onClick={() => runCommand("bold")} title="Полужирный">
|
<Save size={16} />
|
||||||
<Bold size={16} />
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button type="button" onClick={() => runCommand("italic")} title="Курсив">
|
className="word-icon-command"
|
||||||
<Italic size={16} />
|
type="button"
|
||||||
</button>
|
onClick={() => downloadTextFile(document.title.replace(/\.qdocx$/i, ".html"), document.html, "text/html")}
|
||||||
<button type="button" onClick={() => runCommand("underline")} title="Подчеркнутый">
|
title="Сохранить как HTML"
|
||||||
<Underline size={16} />
|
aria-label="Сохранить как HTML"
|
||||||
</button>
|
>
|
||||||
<button type="button" onClick={() => runCommand("strikeThrough")} title="Зачеркнутый" aria-label="Зачеркнутый">
|
<Download size={16} />
|
||||||
<Strikethrough size={16} />
|
</button>
|
||||||
</button>
|
<button className="word-icon-command" type="button" onClick={() => void exportPdf()} title="Экспорт PDF" aria-label="Экспорт PDF">
|
||||||
<button type="button" onClick={() => runCommand("insertUnorderedList")} title="Маркированный список">
|
<FileText size={16} />
|
||||||
<List size={16} />
|
</button>
|
||||||
</button>
|
</div>
|
||||||
<button type="button" onClick={insertLink} title="Вставить ссылку">
|
<span className="word-ribbon-title">Файл</span>
|
||||||
<Link size={16} />
|
</section>
|
||||||
</button>
|
|
||||||
<button type="button" onClick={insertPageBreak} title="Разрыв страницы" aria-label="Разрыв страницы">
|
<section className="word-ribbon-group word-clipboard-group" aria-label="Буфер обмена">
|
||||||
<SeparatorHorizontal size={16} />
|
<button className="word-large-command" type="button" onClick={() => runCommand("paste")} title="Вставить">
|
||||||
</button>
|
<ClipboardPaste size={22} />
|
||||||
<button type="button" onClick={() => imageInputRef.current?.click()} title="Вставить изображение">
|
<span>Вставить</span>
|
||||||
<ImageIcon size={16} />
|
</button>
|
||||||
</button>
|
<div className="word-command-stack">
|
||||||
<button type="button" onClick={() => runCommand("justifyLeft")} title="По левому краю">
|
<button type="button" onClick={() => runCommand("cut")} title="Вырезать">
|
||||||
<AlignLeft size={16} />
|
<Scissors size={15} />
|
||||||
</button>
|
Вырезать
|
||||||
<button type="button" onClick={() => runCommand("justifyCenter")} title="По центру">
|
</button>
|
||||||
<AlignCenter size={16} />
|
<button type="button" onClick={() => runCommand("copy")} title="Копировать">
|
||||||
</button>
|
<Copy size={15} />
|
||||||
<button type="button" onClick={() => runCommand("justifyRight")} title="По правому краю">
|
Копировать
|
||||||
<AlignRight size={16} />
|
</button>
|
||||||
</button>
|
<button type="button" onClick={() => runCommand("removeFormat")} title="Очистить формат">
|
||||||
|
<RemoveFormatting size={15} />
|
||||||
|
Очистить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span className="word-ribbon-title">Буфер обмена</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="word-ribbon-group word-font-group" aria-label="Шрифт">
|
||||||
|
<div className="word-select-row">
|
||||||
|
<select className="word-font-select" aria-label="Шрифт" defaultValue="Calibri" onChange={(event) => applyFontFamily(event.target.value)}>
|
||||||
|
{fontFamilyOptions.map((fontFamily) => (
|
||||||
|
<option key={fontFamily} value={fontFamily}>
|
||||||
|
{fontFamily}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<select className="word-font-size-select" aria-label="Размер шрифта" defaultValue="11" onChange={(event) => applyFontSize(event.target.value)}>
|
||||||
|
{fontSizeOptions.map((fontSize) => (
|
||||||
|
<option key={fontSize} value={fontSize}>
|
||||||
|
{fontSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="segmented-tools word-format-tools" aria-label="Форматирование текста">
|
||||||
|
<button type="button" onClick={() => runCommand("bold")} title="Полужирный">
|
||||||
|
<Bold size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("italic")} title="Курсив">
|
||||||
|
<Italic size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("underline")} title="Подчеркнутый">
|
||||||
|
<Underline size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("strikeThrough")} title="Зачеркнутый" aria-label="Зачеркнутый">
|
||||||
|
<Strikethrough size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("subscript")} title="Нижний индекс">
|
||||||
|
<Subscript size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("superscript")} title="Верхний индекс">
|
||||||
|
<Superscript size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="word-color-row">
|
||||||
|
<div className="text-color-tools" aria-label="Цвет текста">
|
||||||
|
<Type size={16} aria-hidden="true" />
|
||||||
|
{textColorSwatches.map((swatch) => (
|
||||||
|
<button
|
||||||
|
key={swatch.label}
|
||||||
|
className="color-swatch"
|
||||||
|
type="button"
|
||||||
|
onClick={() => applyTextColor(swatch.color)}
|
||||||
|
title={swatch.label}
|
||||||
|
aria-label={swatch.label}
|
||||||
|
style={{ backgroundColor: swatch.color }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="text-color-tools" aria-label="Подсветка текста">
|
||||||
|
<PaintBucket size={16} aria-hidden="true" />
|
||||||
|
{highlightSwatches.map((swatch) => (
|
||||||
|
<button
|
||||||
|
key={swatch.label}
|
||||||
|
className="color-swatch"
|
||||||
|
type="button"
|
||||||
|
onClick={() => applyHighlight(swatch.color)}
|
||||||
|
title={swatch.label}
|
||||||
|
aria-label={swatch.label}
|
||||||
|
style={{ backgroundColor: swatch.color }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="word-ribbon-title">Шрифт</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="word-ribbon-group word-paragraph-group" aria-label="Абзац">
|
||||||
|
<div className="segmented-tools" aria-label="Списки">
|
||||||
|
<button type="button" onClick={() => runCommand("insertUnorderedList")} title="Маркированный список">
|
||||||
|
<List size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("insertOrderedList")} title="Нумерованный список">
|
||||||
|
<ListOrdered size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="segmented-tools" aria-label="Выравнивание">
|
||||||
|
<button type="button" onClick={() => runCommand("justifyLeft")} title="По левому краю">
|
||||||
|
<AlignLeft size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("justifyCenter")} title="По центру">
|
||||||
|
<AlignCenter size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("justifyRight")} title="По правому краю">
|
||||||
|
<AlignRight size={16} />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => runCommand("justifyFull")} title="По ширине">
|
||||||
|
<AlignJustify size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button className="compact-command" type="button" onClick={insertPageBreak} title="Разрыв страницы" aria-label="Разрыв страницы">
|
||||||
|
<SeparatorHorizontal size={16} />
|
||||||
|
Разрыв
|
||||||
|
</button>
|
||||||
|
<span className="word-ribbon-title">Абзац</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="word-ribbon-group word-styles-group" aria-label="Стили">
|
||||||
|
<div className="word-style-gallery">
|
||||||
|
{styleGalleryOptions.map((style) => (
|
||||||
|
<button key={style.value} type="button" onClick={() => runCommand("formatBlock", style.value)}>
|
||||||
|
<span>AaБбВв</span>
|
||||||
|
{style.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<span className="word-ribbon-title">Стили</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="word-ribbon-group word-insert-group" aria-label="Вставка">
|
||||||
|
<button className="compact-command" type="button" onClick={insertLink}>
|
||||||
|
<Link size={16} />
|
||||||
|
Ссылка
|
||||||
|
</button>
|
||||||
|
<button className="compact-command" type="button" onClick={() => imageInputRef.current?.click()}>
|
||||||
|
<ImageIcon size={16} />
|
||||||
|
Рисунок
|
||||||
|
</button>
|
||||||
|
<span className="word-ribbon-title">Вставка</span>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="word-ribbon-group word-edit-group" aria-label="Редактирование">
|
||||||
|
<button className="compact-command" type="button" onClick={findText}>
|
||||||
|
<Search size={16} />
|
||||||
|
Найти
|
||||||
|
</button>
|
||||||
|
<button className="compact-command" type="button" onClick={replaceText}>
|
||||||
|
<Replace size={16} />
|
||||||
|
Заменить
|
||||||
|
</button>
|
||||||
|
<span className="toolbar-status">
|
||||||
|
Страниц: {pageView.count} · Слов: {wordCount}
|
||||||
|
</span>
|
||||||
|
<span className="word-ribbon-title">Редактирование</span>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-color-tools" aria-label="Цвет текста">
|
|
||||||
<Type size={16} aria-hidden="true" />
|
|
||||||
{textColorSwatches.map((swatch) => (
|
|
||||||
<button
|
|
||||||
key={swatch.label}
|
|
||||||
className="color-swatch"
|
|
||||||
type="button"
|
|
||||||
onClick={() => applyTextColor(swatch.color)}
|
|
||||||
title={swatch.label}
|
|
||||||
aria-label={swatch.label}
|
|
||||||
style={{ backgroundColor: swatch.color }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="text-color-tools" aria-label="Подсветка текста">
|
|
||||||
<PaintBucket size={16} aria-hidden="true" />
|
|
||||||
{highlightSwatches.map((swatch) => (
|
|
||||||
<button
|
|
||||||
key={swatch.label}
|
|
||||||
className="color-swatch"
|
|
||||||
type="button"
|
|
||||||
onClick={() => applyHighlight(swatch.color)}
|
|
||||||
title={swatch.label}
|
|
||||||
aria-label={swatch.label}
|
|
||||||
style={{ backgroundColor: swatch.color }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<select aria-label="Стиль абзаца" onChange={(event) => runCommand("formatBlock", event.target.value)}>
|
|
||||||
<option value="p">Обычный текст</option>
|
|
||||||
<option value="h1">Заголовок 1</option>
|
|
||||||
<option value="h2">Заголовок 2</option>
|
|
||||||
<option value="blockquote">Цитата</option>
|
|
||||||
</select>
|
|
||||||
<button className="compact-command" type="button" onClick={() => void chooseDocx()}>
|
|
||||||
<FileUp size={16} />
|
|
||||||
Открыть DOCX
|
|
||||||
</button>
|
|
||||||
<button className="compact-command" type="button" onClick={() => void saveDocx()}>
|
|
||||||
<Save size={16} />
|
|
||||||
Сохранить DOCX
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="compact-command"
|
|
||||||
type="button"
|
|
||||||
onClick={() => downloadTextFile(document.title.replace(/\.qdocx$/i, ".html"), document.html, "text/html")}
|
|
||||||
>
|
|
||||||
<Download size={16} />
|
|
||||||
Экспорт HTML
|
|
||||||
</button>
|
|
||||||
<button className="compact-command" type="button" onClick={() => void exportPdf()}>
|
|
||||||
<FileText size={16} />
|
|
||||||
Экспорт PDF
|
|
||||||
</button>
|
|
||||||
<span className="toolbar-status">
|
|
||||||
Страниц: {pageView.count} · Слов: {wordCount}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="page-ruler" aria-hidden="true">
|
<div className="page-ruler" aria-hidden="true">
|
||||||
|
|||||||
+236
@@ -327,6 +327,242 @@ button {
|
|||||||
background: rgba(255, 255, 255, 0.96);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.word-module-toolbar {
|
||||||
|
display: block;
|
||||||
|
min-height: 118px;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-ribbon {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 118px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-ribbon-group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: minmax(0, 1fr) 18px;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 118px;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 8px 10px 4px;
|
||||||
|
border-right: 1px solid #d6dde8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-ribbon-title {
|
||||||
|
align-self: end;
|
||||||
|
color: #69778a;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-command-row,
|
||||||
|
.word-select-row,
|
||||||
|
.word-color-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-file-group {
|
||||||
|
flex: 0 0 76px;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-file-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 28px);
|
||||||
|
align-content: start;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-icon-command {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-icon-command:hover {
|
||||||
|
border-color: #c7d8eb;
|
||||||
|
background: #edf6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-clipboard-group {
|
||||||
|
flex: 0 0 150px;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-clipboard-group .word-ribbon-title,
|
||||||
|
.word-font-group .word-ribbon-title,
|
||||||
|
.word-paragraph-group .word-ribbon-title,
|
||||||
|
.word-styles-group .word-ribbon-title,
|
||||||
|
.word-insert-group .word-ribbon-title,
|
||||||
|
.word-edit-group .word-ribbon-title {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-large-command {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
align-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 66px;
|
||||||
|
min-height: 70px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-large-command:hover,
|
||||||
|
.word-command-stack button:hover,
|
||||||
|
.word-style-gallery button:hover {
|
||||||
|
border-color: #c7d8eb;
|
||||||
|
background: #edf6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-command-stack {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-command-stack button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 22px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-font-group {
|
||||||
|
flex: 1 1 266px;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-font-select {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 170px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-font-size-select {
|
||||||
|
flex: 0 0 64px;
|
||||||
|
width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-format-tools {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-color-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-color-row .text-color-tools {
|
||||||
|
min-height: 26px;
|
||||||
|
padding-left: 0;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-color-row .color-swatch {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-paragraph-group {
|
||||||
|
flex: 0 0 170px;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-paragraph-group > .segmented-tools,
|
||||||
|
.word-paragraph-group > .compact-command {
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-paragraph-group .compact-command {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-styles-group {
|
||||||
|
flex: 0 1 210px;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-style-gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(74px, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-style-gallery button {
|
||||||
|
display: grid;
|
||||||
|
align-content: center;
|
||||||
|
min-height: 64px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid #d6dde8;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #334155;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-style-gallery button span {
|
||||||
|
color: #1e5fa7;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-insert-group {
|
||||||
|
flex: 0 0 96px;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-insert-group .compact-command,
|
||||||
|
.word-edit-group .compact-command {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-edit-group {
|
||||||
|
flex: 0 0 132px;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-edit-group .toolbar-status {
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 0;
|
||||||
|
border-left: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.hidden-file-input {
|
.hidden-file-input {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user