富文本编辑器,可以对图片、文字进行编辑。
| Web | 微信小程序 | Android | iOS | HarmonyOS | HarmonyOS(Vapor) |
|---|---|---|---|---|---|
| 4.0 | 4.41 | 5.08 | 5.08 | 5.08 | 5.08 |
| 名称 | 类型 | 默认值 | 兼容性 | 描述 |
|---|---|---|---|---|
| read-only | boolean | - | 设置编辑器为只读 | |
| placeholder | string | - | 提示信息 | |
| show-img-size | boolean | - | 点击图片时显示图片大小控件 | |
| show-img-toolbar | boolean | - | 点击图片时显示工具栏控件 | |
| show-img-resize | string | - | 点击图片时显示修改尺寸控件 | |
| enable-formats | Array.<string> | - | (Array.<string>) 编辑器允许的名单内的格式 | |
| type | "none" | "null" | 输入类型,暂只支持 none(不弹出键盘) | |
| @ready | (event: UniEvent) => void | - | 编辑器初始化完成时触发 | |
| @focus | (event: UniEvent) => void | - | 编辑器聚焦时触发,event.detail = {html, text, delta} | |
| @blur | (event: UniEvent) => void | - | 编辑器失去焦点时触发,detail = {html, text, delta} | |
| @input | (event: UniEvent) => void | - | 编辑器内容改变时触发,detail = {html, text, delta} | |
| @statuschange | (event: UniEvent) => void | - | 通过 Context 方法改变编辑器内样式时触发,返回选区已设置的样式 |
editor组件有上下文对象,api为uni.createEditorContextAsync()。
给editor组件设一个id属性,将id的值传入uni.createEditorContextAsync(),即可得到editor组件的上下文对象,进一步可使用对象上的方法。
示例为hello uni-app x alpha分支,与最新HBuilderX Alpha版同步。与最新正式版同步的master分支示例另见
示例
<template>
<view id="editor-page-shell" :class="pageShellClass">
<view id="editor-layout" :class="editorLayoutClass">
<view
class="toolbar-wrap"
:style="toolbarWrapStyle"
<!--#ifdef WEB -->
@mousedown="onToolbarMouseDown"
<!--#endif-->
>
<view class="toolbar-frame">
<view :class="desktopToolbarShellClass" @mousemove="hideToolbarTooltip">
<view :class="desktopToolbarLeadingGroupClass">
<view id="toolbar-button-undo" @tap="undo" @mousemove.stop="showToolbarTooltip('toolbar-button-undo', '撤回')" class="toolbar-icon-button">
<text class="toolbar-icon-text iconfont">{{""}}</text>
</view>
<view id="toolbar-button-redo" @tap="redo" @mousemove.stop="showToolbarTooltip('toolbar-button-redo', '重做')" class="toolbar-icon-button">
<text class="toolbar-icon-text iconfont">{{""}}</text>
</view>
<view id="toolbar-button-clear-format" @tap="clearFormatting" @mousemove.stop="showToolbarTooltip('toolbar-button-clear-format', '清除格式')" class="toolbar-icon-button">
<text class="toolbar-icon-text iconfont">{{""}}</text>
</view>
</view>
<view :class="desktopToolbarDividerClass"></view>
<view :class="desktopToolbarGroupClass">
<view id="toolbar-button-font-size" @tap="openFontSizeSheet" @mousemove.stop="showToolbarTooltip('toolbar-button-font-size', '设置字号')" :class="fontSizeSheetHighlighted ? 'toolbar-select toolbar-select-active' : 'toolbar-select'">
<text :class="fontSizeSheetHighlighted ? 'toolbar-select-label toolbar-select-label-active toolbar-size-label' : 'toolbar-select-label toolbar-size-label'">{{ getDesktopFontSizeLabel() }}</text>
<text :class="fontSizeSheetHighlighted ? 'toolbar-select-arrow toolbar-select-arrow-active iconfont' : 'toolbar-select-arrow iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-font-family" @tap="openFontFamilySheet" @mousemove.stop="showToolbarTooltip('toolbar-button-font-family', '设置字体')" :class="fontFamilySheetHighlighted ? 'toolbar-select toolbar-select-font toolbar-select-active' : 'toolbar-select toolbar-select-font'">
<!-- <text :class="fontFamilySheetHighlighted ? 'toolbar-icon-text toolbar-icon-text-active toolbar-select-glyph iconfont' : 'toolbar-icon-text toolbar-select-glyph iconfont'">{{""}}</text> -->
<text :class="fontFamilySheetHighlighted ? 'toolbar-select-label toolbar-select-label-active toolbar-select-font-label' : 'toolbar-select-label toolbar-select-font-label'">{{ getDesktopFontFamilyLabel() }}</text>
<text :class="fontFamilySheetHighlighted ? 'toolbar-select-arrow toolbar-select-arrow-active iconfont' : 'toolbar-select-arrow iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-bold" @tap="toggleBold" @mousemove.stop="showToolbarTooltip('toolbar-button-bold', '加粗')" :class="data.formats.bold ? 'toolbar-icon-button toolbar-icon-button-active' : 'toolbar-icon-button'">
<text :class="data.formats.bold ? 'toolbar-icon-text toolbar-icon-text-active toolbar-font-action iconfont relative-top-1' : 'toolbar-icon-text toolbar-font-action iconfont relative-top-1'">{{""}}</text>
</view>
<view id="toolbar-button-italic" @tap="toggleItalic" @mousemove.stop="showToolbarTooltip('toolbar-button-italic', '斜体')" :class="data.formats.italic ? 'toolbar-icon-button toolbar-icon-button-active' : 'toolbar-icon-button'">
<text :class="data.formats.italic ? 'toolbar-icon-text toolbar-icon-text-active toolbar-font-action iconfont relative-top-1' : 'toolbar-icon-text toolbar-font-action iconfont relative-top-1'">{{""}}</text>
</view>
<view id="toolbar-button-underline" @tap="toggleUnderline" @mousemove.stop="showToolbarTooltip('toolbar-button-underline', '下划线')" :class="data.formats.underline ? 'toolbar-icon-button toolbar-icon-button-active' : 'toolbar-icon-button'">
<text :class="data.formats.underline ? 'toolbar-icon-text toolbar-icon-text-active toolbar-font-action iconfont' : 'toolbar-icon-text toolbar-font-action iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-strike" @tap="toggleStrike" @mousemove.stop="showToolbarTooltip('toolbar-button-strike', '删除线')" :class="data.formats.strike ? 'toolbar-icon-button toolbar-icon-button-active' : 'toolbar-icon-button'">
<text :class="data.formats.strike ? 'toolbar-icon-text toolbar-icon-text-active toolbar-font-action iconfont relative-top-1' : 'toolbar-icon-text toolbar-font-action iconfont relative-top-1'">{{""}}</text>
</view>
</view>
<view :class="desktopToolbarDividerClass"></view>
<view :class="desktopToolbarGroupClass">
<view id="toolbar-button-text-color" @tap="openTextColorSheet" @mousemove.stop="showToolbarTooltip('toolbar-button-text-color', '字体颜色')" :class="textColorSheetHighlighted ? 'toolbar-color-button toolbar-color-button-active' : 'toolbar-color-button'">
<view class="toolbar-color-stack">
<text :class="textColorSheetHighlighted ? 'toolbar-color-symbol toolbar-color-symbol-active iconfont' : 'toolbar-color-symbol iconfont'">{{""}}</text>
<view class="toolbar-color-underline" :style="toolbarTextColorChipStyle"></view>
</view>
<text :class="textColorSheetHighlighted ? 'toolbar-color-arrow toolbar-color-arrow-active iconfont' : 'toolbar-color-arrow iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-background-color" @tap="openBackgroundColorSheet" @mousemove.stop="showToolbarTooltip('toolbar-button-background-color', '背景颜色')" :class="backgroundColorSheetHighlighted ? 'toolbar-color-button toolbar-color-button-active' : 'toolbar-color-button'">
<view class="toolbar-color-stack">
<text :class="backgroundColorSheetHighlighted ? 'toolbar-color-symbol toolbar-color-symbol-active iconfont' : 'toolbar-color-symbol iconfont'">{{""}}</text>
<view class="toolbar-color-underline" :style="toolbarBackgroundColorChipStyle"></view>
</view>
<text :class="backgroundColorSheetHighlighted ? 'toolbar-color-arrow toolbar-color-arrow-active iconfont' : 'toolbar-color-arrow iconfont'" style="margin-left: 2px">{{""}}</text>
</view>
</view>
<view :class="desktopToolbarDividerClass"></view>
<view :class="desktopToolbarGroupClass">
<view id="toolbar-button-align" @tap="openAlignSheet" @mousemove.stop="showToolbarTooltip('toolbar-button-align', '对齐方式')" :class="alignSheetHighlighted ? 'toolbar-select toolbar-select-compact toolbar-select-active' : 'toolbar-select toolbar-select-compact'">
<text :class="alignSheetHighlighted ? 'toolbar-icon-text toolbar-icon-text-active toolbar-select-glyph iconfont' : 'toolbar-icon-text toolbar-select-glyph iconfont'">{{ desktopAlignIconGlyph }}</text>
<text :class="alignSheetHighlighted ? 'toolbar-select-arrow toolbar-select-arrow-active iconfont' : 'toolbar-select-arrow iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-text-indent" @tap="toggleTextIndent" @mousemove.stop="showToolbarTooltip('toolbar-button-text-indent', '首行缩进')" :class="textIndentHighlighted ? 'toolbar-icon-button toolbar-icon-button-active' : 'toolbar-icon-button'">
<text :class="textIndentHighlighted ? 'toolbar-icon-text toolbar-icon-text-active iconfont' : 'toolbar-icon-text iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-block-indent" @tap="openBlockIndentSheet" @mousemove.stop="showToolbarTooltip('toolbar-button-block-indent', '两端缩进')" :class="blockIndentSheetHighlighted ? 'toolbar-select toolbar-select-compact toolbar-select-active' : 'toolbar-select toolbar-select-compact'">
<text :class="blockIndentSheetHighlighted ? 'toolbar-icon-text toolbar-icon-text-active toolbar-select-glyph iconfont' : 'toolbar-icon-text toolbar-select-glyph iconfont'">{{""}}</text>
<text :class="blockIndentSheetHighlighted ? 'toolbar-select-arrow toolbar-select-arrow-active iconfont' : 'toolbar-select-arrow iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-line-height" @tap="openLineHeightSheet" @mousemove.stop="showToolbarTooltip('toolbar-button-line-height', '行间距')" :class="lineHeightSheetHighlighted ? 'toolbar-select toolbar-select-compact toolbar-select-value toolbar-select-active' : 'toolbar-select toolbar-select-compact toolbar-select-value'">
<text :class="lineHeightSheetHighlighted ? 'toolbar-icon-text toolbar-icon-text-active toolbar-select-glyph iconfont' : 'toolbar-icon-text toolbar-select-glyph iconfont'">{{""}}</text>
<text :class="lineHeightSheetHighlighted ? 'toolbar-select-arrow toolbar-select-arrow-active iconfont' : 'toolbar-select-arrow iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-letter-spacing" @tap="openLetterSpacingSheet" @mousemove.stop="showToolbarTooltip('toolbar-button-letter-spacing', '字间距')" :class="letterSpacingSheetHighlighted ? 'toolbar-select toolbar-select-compact toolbar-select-value toolbar-select-active' : 'toolbar-select toolbar-select-compact toolbar-select-value'">
<text :class="letterSpacingSheetHighlighted ? 'toolbar-icon-text toolbar-icon-text-active toolbar-select-glyph iconfont' : 'toolbar-icon-text toolbar-select-glyph iconfont'">{{""}}</text>
<text :class="letterSpacingSheetHighlighted ? 'toolbar-select-arrow toolbar-select-arrow-active iconfont' : 'toolbar-select-arrow iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-list" @tap="openListSheet" @mousemove.stop="showToolbarTooltip('toolbar-button-list', '列表')" :class="listSheetHighlighted ? 'toolbar-select toolbar-select-compact toolbar-select-active' : 'toolbar-select toolbar-select-compact'">
<text :class="listSheetHighlighted ? 'toolbar-icon-text toolbar-icon-text-active toolbar-select-glyph iconfont' : 'toolbar-icon-text toolbar-select-glyph iconfont'">{{""}}</text>
<text :class="listSheetHighlighted ? 'toolbar-select-arrow toolbar-select-arrow-active iconfont' : 'toolbar-select-arrow iconfont'">{{""}}</text>
</view>
<!-- #ifndef MP -->
<view id="toolbar-button-blockquote" @tap="toggleBlockquote" @mousemove.stop="showToolbarTooltip('toolbar-button-blockquote', '引用')" :class="data.formats.blockquote ? 'toolbar-icon-button toolbar-icon-button-active' : 'toolbar-icon-button'">
<text :class="data.formats.blockquote ? 'toolbar-icon-text toolbar-icon-text-active iconfont' : 'toolbar-icon-text iconfont'">{{""}}</text>
</view>
<view id="toolbar-button-code-block" @tap="toggleCodeBlock" @mousemove.stop="showToolbarTooltip('toolbar-button-code-block', '代码块')" :class="data.formats.codeBlock ? 'toolbar-icon-button toolbar-icon-button-active' : 'toolbar-icon-button'">
<text :class="data.formats.codeBlock ? 'toolbar-icon-text toolbar-icon-text-active iconfont' : 'toolbar-icon-text iconfont'">{{""}}</text>
</view>
<!-- #endif -->
<view id="toolbar-button-divider" @tap="insertDivider" @mousemove.stop="showToolbarTooltip('toolbar-button-divider', '分割线')" class="toolbar-icon-button">
<text class="toolbar-icon-text iconfont">{{""}}</text>
</view>
</view>
<view :class="desktopToolbarDividerClass"></view>
<view :class="desktopToolbarGroupClass">
<view id="toolbar-button-image" @tap="chooseInsertImage" @mousemove.stop="showToolbarTooltip('toolbar-button-image', '图片')" class="toolbar-icon-button">
<text class="toolbar-icon-text iconfont">{{""}}</text>
</view>
<!-- #ifndef MP -->
<view id="toolbar-button-link" @tap="openLinkModal" @mousemove.stop="showToolbarTooltip('toolbar-button-link', data.formats.link != '' ? '编辑超链接' : '插入超链接')" :class="data.formats.link != '' ? 'toolbar-icon-button toolbar-icon-button-active' : 'toolbar-icon-button'">
<text :class="data.formats.link != '' ? 'toolbar-icon-text toolbar-icon-text-active iconfont' : 'toolbar-icon-text iconfont'">{{""}}</text>
</view>
<!-- #endif -->
</view>
</view>
<!-- #ifdef WEB -->
<view v-if="data.tooltipVisible" class="toolbar-tooltip" :style="toolbarTooltipStyle">
<view class="toolbar-tooltip-arrow"></view>
<view class="toolbar-tooltip-bubble">
<text class="toolbar-tooltip-text">{{ data.tooltipText }}</text>
</view>
</view>
<view v-if="!data.isFocused && data.activeSheet == ''" class="desktop-toolbar-mask"></view>
<!-- #endif -->
</view>
</view>
<view v-if="data.activeSheet != ''" class="sheet-backdrop" @tap="closeSheets"></view>
<view
id="editor-sheet-host"
:class="sheetHostClass"
:style="sheetHostStyle"
v-if="data.activeSheet != ''"
<!--#ifdef WEB -->
@mousedown="onToolbarMouseDown"
<!--#endif-->
>
<view :class="toolbarPanelClass" :style="sheetPanelStyle">
<!-- #ifdef WEB -->
<view v-if="!webPhoneLayout" class="toolbar-panel-arrow" :style="toolbarPanelArrowStyle"></view>
<!-- #endif -->
<view :class="toolbarPanelHeaderClass">
<view class="toolbar-panel-title-wrap">
<text :class="toolbarPanelTitleClass">{{ getPanelTitle() }}</text>
<text :class="toolbarPanelSubtitleClass">{{ getPanelSubtitle() }}</text>
</view>
</view>
<scroll-view :class="toolbarPanelScrollClass" direction="vertical" :show-scrollbar="false">
<view class="toolbar-panel-section" v-if="data.activeSheet == 'title'">
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="setParagraph" :class="(paragraphActive) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(paragraphActive) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">P</text><text :class="(paragraphActive) ? desktopMenuTextActiveClass : desktopMenuTextClass">正文</text></view></view>
<!-- #ifndef MP -->
<view :class="data.toolbarChipCellClass"><view @tap="toggleBlockquote" :class="(data.formats.blockquote) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.blockquote) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">BQ</text><text :class="(data.formats.blockquote) ? desktopMenuTextActiveClass : desktopMenuTextClass">引用</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="toggleCodeBlock" :class="(data.formats.codeBlock) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.codeBlock) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass"></></text><text :class="(data.formats.codeBlock) ? desktopMenuTextActiveClass : desktopMenuTextClass">代码</text></view></view>
<!-- #endif -->
<view :class="data.toolbarChipCellClass"><view @tap="setHeadingLevel(1)" :class="(data.formats.header == 1) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.header == 1) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">H1</text><text :class="(data.formats.header == 1) ? desktopMenuTextActiveClass : desktopMenuTextClass">大标题1</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setHeadingLevel(2)" :class="(data.formats.header == 2) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.header == 2) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">H2</text><text :class="(data.formats.header == 2) ? desktopMenuTextActiveClass : desktopMenuTextClass">大标题2</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setHeadingLevel(3)" :class="(data.formats.header == 3) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.header == 3) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">H3</text><text :class="(data.formats.header == 3) ? desktopMenuTextActiveClass : desktopMenuTextClass">大标题3</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setHeadingLevel(4)" :class="(data.formats.header == 4) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.header == 4) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">H4</text><text :class="(data.formats.header == 4) ? desktopMenuTextActiveClass : desktopMenuTextClass">大标题4</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setHeadingLevel(5)" :class="(data.formats.header == 5) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.header == 5) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">H5</text><text :class="(data.formats.header == 5) ? desktopMenuTextActiveClass : desktopMenuTextClass">大标题5</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setHeadingLevel(6)" :class="(data.formats.header == 6) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.header == 6) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">H6</text><text :class="(data.formats.header == 6) ? desktopMenuTextActiveClass : desktopMenuTextClass">大标题6</text></view></view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'style'">
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="toggleBoldAndClose" :class="(data.formats.bold) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.bold) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">B</text><text :class="(data.formats.bold) ? desktopMenuTextActiveClass : desktopMenuTextClass">加粗</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="toggleItalicAndClose" :class="(data.formats.italic) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.italic) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">I</text><text :class="(data.formats.italic) ? desktopMenuTextActiveClass : desktopMenuTextClass">斜体</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="toggleUnderlineAndClose" :class="(data.formats.underline) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.underline) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">U</text><text :class="(data.formats.underline) ? desktopMenuTextActiveClass : desktopMenuTextClass">下划线</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="toggleStrikeAndClose" :class="(data.formats.strike) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.strike) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">S</text><text :class="(data.formats.strike) ? desktopMenuTextActiveClass : desktopMenuTextClass">删除线</text></view></view>
</view>
<view class="toolbar-color-section">
<text class="toolbar-color-title">文字颜色</text>
<view class="color-row color-row-panel">
<view @tap="setTextColorAndClose('')" :class="(data.formats.color == '') ? clearColorSwatchActiveClass : clearColorSwatchClass"></view>
<view v-for="color in TEXT_COLOR_PRESETS" :key="color" @tap="setTextColorAndClose(color)" :class="(data.formats.color == color) ? filledColorSwatchActiveClass : filledColorSwatchClass" :style="'background-color: ' + color + ';'"></view>
</view>
</view>
<view class="toolbar-color-section">
<text class="toolbar-color-title">背景颜色</text>
<view class="color-row color-row-panel">
<view @tap="setBackgroundColorAndClose('')" :class="(data.formats.backgroundColor == '') ? clearColorSwatchActiveClass : clearColorSwatchClass"></view>
<view v-for="color in BACKGROUND_COLOR_PRESETS" :key="color" @tap="setBackgroundColorAndClose(color)" :class="(data.formats.backgroundColor == color) ? filledColorSwatchActiveClass : filledColorSwatchClass" :style="'background-color: ' + color + ';'"></view>
</view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'line-height'">
<view class="toolbar-panel-group">
<text class="toolbar-color-title">行间距</text>
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="setLineHeightAndClose('')" :class="(data.formats.lineHeight == '') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.lineHeight == '') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">A</text><text :class="(data.formats.lineHeight == '') ? desktopMenuTextActiveClass : desktopMenuTextClass">默认</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setLineHeightAndClose('1.5')" :class="(data.formats.lineHeight == '1.5') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.lineHeight == '1.5') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">1.5</text><text :class="(data.formats.lineHeight == '1.5') ? desktopMenuTextActiveClass : desktopMenuTextClass">紧凑</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setLineHeightAndClose('1.8')" :class="(data.formats.lineHeight == '1.8') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.lineHeight == '1.8') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">1.8</text><text :class="(data.formats.lineHeight == '1.8') ? desktopMenuTextActiveClass : desktopMenuTextClass">舒适</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setLineHeightAndClose('2')" :class="(data.formats.lineHeight == '2') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.lineHeight == '2') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">2.0</text><text :class="(data.formats.lineHeight == '2') ? desktopMenuTextActiveClass : desktopMenuTextClass">宽松</text></view></view>
</view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'letter-spacing'">
<view class="toolbar-panel-group">
<text class="toolbar-color-title">字间距</text>
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="setLetterSpacingAndClose('')" :class="(data.formats.letterSpacing == '') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.letterSpacing == '') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">Aa</text><text :class="(data.formats.letterSpacing == '') ? desktopMenuTextActiveClass : desktopMenuTextClass">默认</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setLetterSpacingAndClose('0px')" :class="(data.formats.letterSpacing == '0px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.letterSpacing == '0px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">0</text><text :class="(data.formats.letterSpacing == '0px') ? desktopMenuTextActiveClass : desktopMenuTextClass">标准</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setLetterSpacingAndClose('1px')" :class="(data.formats.letterSpacing == '1px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.letterSpacing == '1px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">+1</text><text :class="(data.formats.letterSpacing == '1px') ? desktopMenuTextActiveClass : desktopMenuTextClass">微松</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setLetterSpacingAndClose('2px')" :class="(data.formats.letterSpacing == '2px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.letterSpacing == '2px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">+2</text><text :class="(data.formats.letterSpacing == '2px') ? desktopMenuTextActiveClass : desktopMenuTextClass">通透</text></view></view>
</view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'font-size'">
<view class="toolbar-panel-group">
<text class="toolbar-color-title">字号</text>
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('12px')" :class="(data.formats.fontSize == '12px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '12px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">12</text><text :class="(data.formats.fontSize == '12px') ? desktopMenuTextActiveClass : desktopMenuTextClass">12px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('14px')" :class="(data.formats.fontSize == '14px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '14px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">14</text><text :class="(data.formats.fontSize == '14px') ? desktopMenuTextActiveClass : desktopMenuTextClass">14px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('15px')" :class="(data.formats.fontSize == '15px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '15px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">15</text><text :class="(data.formats.fontSize == '15px') ? desktopMenuTextActiveClass : desktopMenuTextClass">15px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('16px')" :class="(data.formats.fontSize == '16px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '16px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">16</text><text :class="(data.formats.fontSize == '16px') ? desktopMenuTextActiveClass : desktopMenuTextClass">16px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('17px')" :class="(data.formats.fontSize == '17px' || data.formats.fontSize == '') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '17px' || data.formats.fontSize == '') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">17</text><text :class="(data.formats.fontSize == '17px' || data.formats.fontSize == '') ? desktopMenuTextActiveClass : desktopMenuTextClass">默认</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('19px')" :class="(data.formats.fontSize == '19px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '19px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">19</text><text :class="(data.formats.fontSize == '19px') ? desktopMenuTextActiveClass : desktopMenuTextClass">19px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('20px')" :class="(data.formats.fontSize == '20px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '20px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">20</text><text :class="(data.formats.fontSize == '20px') ? desktopMenuTextActiveClass : desktopMenuTextClass">20px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('24px')" :class="(data.formats.fontSize == '24px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '24px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">24</text><text :class="(data.formats.fontSize == '24px') ? desktopMenuTextActiveClass : desktopMenuTextClass">24px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('32px')" :class="(data.formats.fontSize == '32px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '32px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">32</text><text :class="(data.formats.fontSize == '32px') ? desktopMenuTextActiveClass : desktopMenuTextClass">32px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontSizeAndClose('48px')" :class="(data.formats.fontSize == '48px') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontSize == '48px') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">48</text><text :class="(data.formats.fontSize == '48px') ? desktopMenuTextActiveClass : desktopMenuTextClass">48px</text></view></view>
</view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'font-family'">
<view class="toolbar-panel-group">
<text class="toolbar-color-title">字体</text>
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('')" :class="(data.formats.fontFamily == '') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == '') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">D</text><text :class="(data.formats.fontFamily == '') ? desktopMenuTextActiveClass : desktopMenuTextClass">默认</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('Microsoft YaHei')" :class="(data.formats.fontFamily == 'Microsoft YaHei') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == 'Microsoft YaHei') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">雅黑</text><text :class="(data.formats.fontFamily == 'Microsoft YaHei') ? desktopMenuTextActiveClass : desktopMenuTextClass">微软雅黑</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('SimHei')" :class="(data.formats.fontFamily == 'SimHei') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == 'SimHei') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">黑</text><text :class="(data.formats.fontFamily == 'SimHei') ? desktopMenuTextActiveClass : desktopMenuTextClass">黑体</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('SimSun')" :class="(data.formats.fontFamily == 'SimSun') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == 'SimSun') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">宋</text><text :class="(data.formats.fontFamily == 'SimSun') ? desktopMenuTextActiveClass : desktopMenuTextClass">宋体</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('KaiTi')" :class="(data.formats.fontFamily == 'KaiTi') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == 'KaiTi') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">楷</text><text :class="(data.formats.fontFamily == 'KaiTi') ? desktopMenuTextActiveClass : desktopMenuTextClass">楷体</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('FangSong')" :class="(data.formats.fontFamily == 'FangSong') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == 'FangSong') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">仿宋</text><text :class="(data.formats.fontFamily == 'FangSong') ? desktopMenuTextActiveClass : desktopMenuTextClass">仿宋</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('Georgia')" :class="(data.formats.fontFamily == 'Georgia') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == 'Georgia') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">Geo</text><text :class="(data.formats.fontFamily == 'Georgia') ? desktopMenuTextActiveClass : desktopMenuTextClass">Georgia</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('Times New Roman')" :class="(data.formats.fontFamily == 'Times New Roman') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == 'Times New Roman') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">TNR</text><text :class="(data.formats.fontFamily == 'Times New Roman') ? desktopMenuTextActiveClass : desktopMenuTextClass">Times</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setFontFamilyAndClose('Courier New')" :class="(data.formats.fontFamily == 'Courier New') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.fontFamily == 'Courier New') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">Mono</text><text :class="(data.formats.fontFamily == 'Courier New') ? desktopMenuTextActiveClass : desktopMenuTextClass">Courier</text></view></view>
</view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'align'">
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="clearAlign" :class="(data.formats.align == '') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.align == '') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">D</text><text :class="(data.formats.align == '') ? desktopMenuTextActiveClass : desktopMenuTextClass">默认</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setAlignLeft" :class="(data.formats.align == 'left') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.align == 'left') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">L</text><text :class="(data.formats.align == 'left') ? desktopMenuTextActiveClass : desktopMenuTextClass">居左</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setAlignCenter" :class="(data.formats.align == 'center') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.align == 'center') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">C</text><text :class="(data.formats.align == 'center') ? desktopMenuTextActiveClass : desktopMenuTextClass">居中</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setAlignRight" :class="(data.formats.align == 'right') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.align == 'right') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">R</text><text :class="(data.formats.align == 'right') ? desktopMenuTextActiveClass : desktopMenuTextClass">居右</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setAlignJustify" :class="(data.formats.align == 'justify') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.align == 'justify') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">J</text><text :class="(data.formats.align == 'justify') ? desktopMenuTextActiveClass : desktopMenuTextClass">两端</text></view></view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'block-indent'">
<view class="toolbar-panel-group">
<text class="toolbar-color-title">两端缩进</text>
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="setBlockIndentAndClose('0px')" :class="((data.formats.marginLeft == '' && data.formats.marginRight == '')) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="((data.formats.marginLeft == '' && data.formats.marginRight == '')) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">0</text><text :class="((data.formats.marginLeft == '' && data.formats.marginRight == '')) ? desktopMenuTextActiveClass : desktopMenuTextClass">0px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setBlockIndentAndClose('8px')" :class="((data.formats.marginLeft == '8px' && data.formats.marginRight == '8px')) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="((data.formats.marginLeft == '8px' && data.formats.marginRight == '8px')) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">8</text><text :class="((data.formats.marginLeft == '8px' && data.formats.marginRight == '8px')) ? desktopMenuTextActiveClass : desktopMenuTextClass">8px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setBlockIndentAndClose('16px')" :class="((data.formats.marginLeft == '16px' && data.formats.marginRight == '16px')) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="((data.formats.marginLeft == '16px' && data.formats.marginRight == '16px')) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">16</text><text :class="((data.formats.marginLeft == '16px' && data.formats.marginRight == '16px')) ? desktopMenuTextActiveClass : desktopMenuTextClass">16px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setBlockIndentAndClose('24px')" :class="((data.formats.marginLeft == '24px' && data.formats.marginRight == '24px')) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="((data.formats.marginLeft == '24px' && data.formats.marginRight == '24px')) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">24</text><text :class="((data.formats.marginLeft == '24px' && data.formats.marginRight == '24px')) ? desktopMenuTextActiveClass : desktopMenuTextClass">24px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setBlockIndentAndClose('32px')" :class="((data.formats.marginLeft == '32px' && data.formats.marginRight == '32px')) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="((data.formats.marginLeft == '32px' && data.formats.marginRight == '32px')) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">32</text><text :class="((data.formats.marginLeft == '32px' && data.formats.marginRight == '32px')) ? desktopMenuTextActiveClass : desktopMenuTextClass">32px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setBlockIndentAndClose('40px')" :class="((data.formats.marginLeft == '40px' && data.formats.marginRight == '40px')) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="((data.formats.marginLeft == '40px' && data.formats.marginRight == '40px')) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">40</text><text :class="((data.formats.marginLeft == '40px' && data.formats.marginRight == '40px')) ? desktopMenuTextActiveClass : desktopMenuTextClass">40px</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setBlockIndentAndClose('48px')" :class="((data.formats.marginLeft == '48px' && data.formats.marginRight == '48px')) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="((data.formats.marginLeft == '48px' && data.formats.marginRight == '48px')) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">48</text><text :class="((data.formats.marginLeft == '48px' && data.formats.marginRight == '48px')) ? desktopMenuTextActiveClass : desktopMenuTextClass">48px</text></view></view>
</view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'list'">
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="clearListFormat" :class="(data.formats.list == '') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.list == '') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">D</text><text :class="(data.formats.list == '') ? desktopMenuTextActiveClass : desktopMenuTextClass">默认</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setBulletList" :class="(data.formats.list == 'bullet') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.list == 'bullet') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">•</text><text :class="(data.formats.list == 'bullet') ? desktopMenuTextActiveClass : desktopMenuTextClass">无序列表</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="setOrderedList" :class="(data.formats.list == 'ordered') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.list == 'ordered') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">1.</text><text :class="(data.formats.list == 'ordered') ? desktopMenuTextActiveClass : desktopMenuTextClass">有序列表</text></view></view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'text-color'">
<view class="toolbar-color-section">
<view class="desktop-color-sheet-label">
<view class="desktop-color-sheet-icon"></view>
<text class="desktop-color-sheet-label-text">默认颜色</text>
</view>
<view class="color-row color-row-panel">
<view @tap="setTextColorAndClose('')" :class="(data.formats.color == '') ? clearColorSwatchActiveClass : clearColorSwatchClass"></view>
<view v-for="color in TEXT_COLOR_PRESETS" :key="color" @tap="setTextColorAndClose(color)" :class="(data.formats.color == color) ? filledColorSwatchActiveClass : filledColorSwatchClass" :style="'background-color: ' + color + ';'"></view>
</view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'background-color'">
<view class="toolbar-color-section">
<view class="desktop-color-sheet-label">
<view class="desktop-color-sheet-icon"></view>
<text class="desktop-color-sheet-label-text">默认背景</text>
</view>
<view class="color-row color-row-panel">
<view @tap="setBackgroundColorAndClose('')" :class="(data.formats.backgroundColor == '') ? clearColorSwatchActiveClass : clearColorSwatchClass"></view>
<view v-for="color in BACKGROUND_COLOR_PRESETS" :key="color" @tap="setBackgroundColorAndClose(color)" :class="(data.formats.backgroundColor == color) ? filledColorSwatchActiveClass : filledColorSwatchClass" :style="'background-color: ' + color + ';'"></view>
</view>
</view>
</view>
<view class="toolbar-panel-section" v-if="data.activeSheet == 'more'">
<view class="toolbar-panel-group">
<text class="toolbar-color-title">插入内容</text>
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="toggleChecklistTool" :class="(data.formats.list == 'check') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.list == 'check') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">TODO</text><text :class="(data.formats.list == 'check') ? desktopMenuTextActiveClass : desktopMenuTextClass">TODO</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="insertDivider" :class="(false) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(false) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">---</text><text :class="(false) ? desktopMenuTextActiveClass : desktopMenuTextClass">分割线</text></view></view>
<!-- #ifndef MP -->
<!-- <view :class="data.toolbarChipCellClass"><view @tap="openLinkModal" :class="(data.formats.link != '') ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(data.formats.link != '') ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">URL</text><text :class="(data.formats.link != '') ? desktopMenuTextActiveClass : desktopMenuTextClass">超链接</text></view></view> -->
<!-- #endif -->
</view>
</view>
<view class="toolbar-panel-group">
<text class="toolbar-color-title">编辑操作</text>
<view :class="data.toolbarChipRowClass">
<view :class="data.toolbarChipCellClass"><view @tap="clearFormatting" :class="(false) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(false) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">CLR</text><text :class="(false) ? desktopMenuTextActiveClass : desktopMenuTextClass">清除样式</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="undo" :class="(false) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(false) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">↶</text><text :class="(false) ? desktopMenuTextActiveClass : desktopMenuTextClass">撤销</text></view></view>
<view :class="data.toolbarChipCellClass"><view @tap="redo" :class="(false) ? desktopMenuItemActiveClass : desktopMenuItemClass"><text :class="(false) ? desktopMenuBadgeActiveClass : desktopMenuBadgeClass">↷</text><text :class="(false) ? desktopMenuTextActiveClass : desktopMenuTextClass">重做</text></view></view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- #ifndef MP -->
<!-- #ifdef WEB -->
<view v-if="data.linkModalVisible" class="link-modal-mask link-modal-mask-floating" @tap="closeLinkModal">
<view class="link-modal-card link-modal-card-floating" @tap.stop>
<view class="link-modal-header">
<text class="link-modal-title">{{ data.formats.link != '' ? '编辑超链接' : '插入超链接' }}</text>
<text class="link-modal-subtitle">{{ data.formats.link != '' ? '仅修改链接地址' : (data.linkUseSelectionText ? '将为选中文本添加链接' : '填写显示文本与链接地址') }}</text>
</view>
<view class="link-modal-form">
<view v-if="data.formats.link == '' && !data.linkUseSelectionText" class="link-modal-field">
<text class="link-modal-label">显示文本</text>
<input class="link-modal-input" v-model="data.linkDraftText" placeholder="请输入显示文本" :maxlength="-1" />
</view>
<view v-if="data.formats.link == '' && data.linkUseSelectionText" class="link-modal-field">
<text class="link-modal-label">已选中文本</text>
<view class="link-modal-selection-chip">
<text class="link-modal-selection-text">{{ data.linkDraftText }}</text>
</view>
</view>
<view class="link-modal-field">
<text class="link-modal-label">链接地址</text>
<input class="link-modal-input" v-model="data.linkDraftHref" placeholder="https://example.com" :maxlength="-1" />
</view>
</view>
<view class="link-modal-actions">
<view v-if="data.formats.link != ''" class="link-modal-danger" @tap="removeLink">
<text class="link-modal-danger-text">删除链接</text>
</view>
<view class="link-modal-action-group">
<view class="link-modal-button link-modal-button-secondary" @tap="closeLinkModal">
<text class="link-modal-button-text link-modal-button-text-secondary">取消</text>
</view>
<view class="link-modal-button link-modal-button-primary" @tap="confirmLinkModal">
<text class="link-modal-button-text">确定</text>
</view>
</view>
</view>
</view>
</view>
<!-- #endif -->
<!-- #ifndef WEB -->
<view v-if="data.linkModalVisible" class="link-modal-mask link-modal-mask-app" @tap="closeLinkModal">
<view class="link-modal-card link-modal-card-app" @tap.stop>
<view class="link-modal-header">
<text class="link-modal-title link-modal-title-app">{{ data.formats.link != '' ? '编辑超链接' : '插入超链接' }}</text>
<text class="link-modal-subtitle link-modal-subtitle-app">{{ data.formats.link != '' ? '仅修改链接地址' : (data.linkUseSelectionText ? '将为选中文本添加链接' : '填写显示文本与链接地址') }}</text>
</view>
<view class="link-modal-form">
<view v-if="data.formats.link == '' && !data.linkUseSelectionText" class="link-modal-field">
<text class="link-modal-label link-modal-label-app">显示文本</text>
<input class="link-modal-input link-modal-input-app" v-model="data.linkDraftText" placeholder="请输入显示文本" :maxlength="-1" />
</view>
<view v-if="data.formats.link == '' && data.linkUseSelectionText" class="link-modal-field">
<text class="link-modal-label link-modal-label-app">已选中文本</text>
<view class="link-modal-selection-chip link-modal-selection-chip-app">
<text class="link-modal-selection-text link-modal-selection-text-app">{{ data.linkDraftText }}</text>
</view>
</view>
<view class="link-modal-field">
<text class="link-modal-label link-modal-label-app">链接地址</text>
<input class="link-modal-input link-modal-input-app" v-model="data.linkDraftHref" placeholder="https://example.com" :maxlength="-1" />
</view>
</view>
<view class="link-modal-actions link-modal-actions-app">
<!-- 蒸汽模式下闪退 暂时屏蔽 -->
<!-- #ifndef VUE3-VAPOR -->
<view v-if="data.formats.link != ''" class="link-modal-danger link-modal-danger-app" @tap="removeLink">
<text class="link-modal-danger-text link-modal-danger-text-app">删除链接</text>
</view>
<!-- #endif -->
<view class="link-modal-action-group link-modal-action-group-app">
<view class="link-modal-button link-modal-button-secondary link-modal-button-secondary-app" @tap="closeLinkModal">
<text class="link-modal-button-text link-modal-button-text-secondary link-modal-button-text-secondary-app">取消</text>
</view>
<view class="link-modal-button link-modal-button-primary link-modal-button-primary-app" @tap="confirmLinkModal">
<text class="link-modal-button-text link-modal-button-text-app">确定</text>
</view>
</view>
</view>
</view>
</view>
<!-- #endif -->
<!-- #endif -->
<view class="editor-stage" :style="editorStageStyle">
<!-- editor 切换 hidekeyboard: 注释掉 type -->
<editor
id="editor"
class="editor-field"
placeholder="请输入正文内容..."
style="padding: 20px;"
show-img-size
show-img-toolbar
show-img-resize
@ready="onEditorReady"
@focus="onEditorFocus"
@blur="onEditorBlur"
@statuschange="onStatusChange"
/>
</view>
</view>
</view>
</template>
<script setup lang="uts">
type Formats = {
bold: boolean;
italic: boolean;
underline: boolean;
strike: boolean;
blockquote: boolean;
codeBlock: boolean;
link: string;
header: number;
list: string;
align: string;
textIndent: string;
marginLeft: string;
marginRight: string;
lineHeight: string;
letterSpacing: string;
fontFamily: string;
fontSize: string;
color: string;
backgroundColor: string;
}
const TEXT_COLOR_PRESETS: string[] = [
'#000000', '#3f3f3f', '#6a6a6a', '#949494', '#bfbfbf', '#d8d8d8', '#ececec', '#f5f5f5', '#ffffff',
'#f03a37', '#f26c2e', '#f59a33', '#f7d447', '#7bc943', '#59c3c3', '#4a90e2', '#3d5ed7', '#6f42c1', '#d13f96',
'#f3ddd8', '#f7e8da', '#f7ecd3', '#fbf4d0', '#e5f1d4', '#d9eeea', '#d8eaf6', '#dbe3f4', '#e4ddf1', '#f1dde8',
'#ff9d9a', '#ffb389', '#ffcf8a', '#fff17a', '#b8e986', '#7ed6d4', '#8cc8f6', '#9fb6f0', '#c3a5ea', '#f09ac5',
'#ff4d57', '#ff7a45', '#ffad47', '#ffe44d', '#7edc3f', '#32c5c0', '#4aa8f0', '#5b7bea', '#9556d9', '#ec4fa3',
'#d0021b', '#d9480f', '#df7b00', '#d9b300', '#38a000', '#1398a0', '#1769d2', '#2946c7', '#5b2db5', '#c2187a',
'#98001a', '#952700', '#9a4b00', '#8a6a00', '#1f6f00', '#0d5c63', '#0d47a1', '#16269a', '#3c177c', '#8e105f'
]
const BACKGROUND_COLOR_PRESETS: string[] = [
'#ffffff', '#e8e8e8', '#d9d9d9', '#c4c4c4', '#a8a8a8', '#8a8a8a', '#6d6d6d', '#4f4f4f', '#2f2f2f',
'#fff1f0', '#fff4ec', '#fff7e8', '#fffbe6', '#f4fde8', '#edfdfb', '#eef7ff', '#f1f4ff', '#f5f0ff', '#fff0f7',
'#ffd9d6', '#ffe3cf', '#ffecc8', '#fff5bf', '#e6f7c9', '#d8f3f2', '#d9ecff', '#dfe6ff', '#eadfff', '#ffd9ee',
'#ffb8b5', '#ffc89f', '#ffd997', '#fff08a', '#d0f0a8', '#b8e9e7', '#b8dcff', '#c4d2ff', '#d9c2ff', '#ffb9de',
'#ff8b87', '#ffa96f', '#ffc15f', '#ffe65c', '#a9e45f', '#86d9d5', '#8fc4ff', '#9eb5ff', '#c29af5', '#ff8cc8',
'#ff5f5b', '#ff8a3d', '#ffaa2b', '#ffd400', '#7fd13b', '#42bebc', '#5da9ff', '#738ef5', '#9a68e0', '#ef5aae',
'#ffdede', '#ffe9dd', '#fff1e0', '#fff7db', '#edf8df', '#e1f5f3', '#e3f0ff', '#e7ebff', '#efe8ff', '#ffe5f3'
]
type DataType = {
pageWindowHeight: number;
pageWindowWidth: number;
toolbarChipRowClass: string;
toolbarChipCellClass: string;
tooltipVisible: boolean;
tooltipText: string;
tooltipLeft: number;
tooltipTop: number;
sheetHeight: number;
sheetTop: number;
sheetLeft: number;
sheetArrowLeft: number;
sheetAnimated: boolean;
editorInputType: string;
isFocused: boolean;
ignoreNextBlur: boolean;
activeSheet: string;
autoTest: boolean;
editorReadyTest: boolean;
undoTest: boolean;
redoTest: boolean;
removeFormatTest: boolean;
insertImageTest: boolean;
blurTest: boolean;
clearTest: boolean;
getContentDeltaTest: any | null;
formatPainterActive: boolean;
formatPainterFormats: Formats;
formats: Formats;
linkModalVisible: boolean;
linkUseSelectionText: boolean;
linkDraftText: string;
linkDraftHref: string;
linkSelectionText: string;
linkOriginalHref: string;
}
function createDefaultFormats() : Formats {
return {
bold: false,
italic: false,
underline: false,
strike: false,
blockquote: false,
codeBlock: false,
link: '',
header: 0,
list: '',
align: '',
textIndent: '',
marginLeft: '',
marginRight: '',
lineHeight: '',
letterSpacing: '',
fontFamily: '',
fontSize: '',
color: '',
backgroundColor: ''
}
}
function assignFormats(target: Formats, source: Formats) {
target.bold = source.bold
target.italic = source.italic
target.underline = source.underline
target.strike = source.strike
target.blockquote = source.blockquote
target.codeBlock = source.codeBlock
target.link = source.link
target.header = source.header
target.list = source.list
target.align = source.align
target.textIndent = source.textIndent
target.marginLeft = source.marginLeft
target.marginRight = source.marginRight
target.lineHeight = source.lineHeight
target.letterSpacing = source.letterSpacing
target.fontFamily = source.fontFamily
target.fontSize = source.fontSize
target.color = source.color
target.backgroundColor = source.backgroundColor
}
const data = reactive<DataType>({
pageWindowHeight: 0,
pageWindowWidth: 0,
toolbarChipRowClass: 'toolbar-chip-row',
toolbarChipCellClass: 'toolbar-chip-cell',
tooltipVisible: false,
tooltipText: '',
tooltipLeft: 0,
tooltipTop: 0,
sheetHeight: 320,
sheetTop: 74,
sheetLeft: 12,
sheetArrowLeft: 32,
sheetAnimated: false,
editorInputType: '',
isFocused: false,
ignoreNextBlur: false,
activeSheet: '',
autoTest: false,
editorReadyTest: false,
undoTest: false,
redoTest: false,
removeFormatTest: false,
insertImageTest: false,
blurTest: false,
clearTest: false,
getContentDeltaTest: null,
formatPainterActive: false,
formatPainterFormats: createDefaultFormats(),
formats: createDefaultFormats(),
linkModalVisible: false,
linkUseSelectionText: false,
linkDraftText: '',
linkDraftHref: '',
linkSelectionText: '',
linkOriginalHref: ''
})
const editorContext = ref<EditorContext | null>(null)
let toolbarHideTimer = 0
let statusSyncLockedUntil = 0
function clearToolbarHideTimer() {
if (toolbarHideTimer > 0) {
clearTimeout(toolbarHideTimer)
toolbarHideTimer = 0
}
}
function scheduleToolbarHide() {
clearToolbarHideTimer()
// 工具栏改为公共常驻模式,这里仅重置计时器状态。
toolbarHideTimer = 0
}
function getEditor() : EditorContext | null {
return editorContext.value
}
function getToolbarWrapStyle() : string {
return ''
}
function hasInlineStyleActive() : boolean {
return data.formats.bold || data.formats.italic || data.formats.underline || data.formats.strike || data.formats.lineHeight != '' || data.formats.letterSpacing != '' || data.formats.fontSize != '' || data.formats.fontFamily != '' || data.formats.color != '' || data.formats.backgroundColor != ''
}
function getToolbarTooltipStyle() : string {
return `left:${data.tooltipLeft}px;top:${data.tooltipTop}px;`
}
function isWideLayout() : boolean {
return data.pageWindowWidth >= 1120
}
function isWebPhoneLayout() : boolean {
// #ifdef WEB
const windowWidth = data.pageWindowWidth > 0 ? data.pageWindowWidth : uni.getWindowInfo().windowWidth
return windowWidth < 760
// #endif
return false
}
function syncToolbarBottom() {
}
function syncToolbarVisibility() {
}
function getPageShellClass() : string {
if (isWideLayout()) {
return 'page-shell page-shell-wide'
}
return 'page-shell'
}
function getEditorLayoutClass() : string {
if (isWideLayout()) {
return 'editor-layout editor-layout-wide'
}
return 'editor-layout'
}
function getSheetPanelStyle() : string {
return ''
}
function getSheetWidth() : number {
const windowWidth = data.pageWindowWidth > 0 ? data.pageWindowWidth : uni.getWindowInfo().windowWidth
let maxWidth = windowWidth - 24
if (maxWidth < 240) {
maxWidth = windowWidth - 12
}
if (maxWidth < 220) {
return maxWidth
}
if (data.activeSheet == 'title') {
return maxWidth < 280 ? maxWidth : 280
}
if (data.activeSheet == 'style') {
return maxWidth < 440 ? maxWidth : 440
}
if (data.activeSheet == 'text-color' || data.activeSheet == 'background-color') {
return maxWidth < 372 ? maxWidth : 372
}
if (data.activeSheet == 'line-height' || data.activeSheet == 'letter-spacing') {
return maxWidth < 220 ? maxWidth : 220
}
if (data.activeSheet == 'align') {
return maxWidth < 220 ? maxWidth : 220
}
return maxWidth < 260 ? maxWidth : 260
}
function syncToolbarChipClasses() {
const panelWidth = getSheetWidth()
if (panelWidth <= 300) {
data.toolbarChipRowClass = 'toolbar-chip-row toolbar-chip-row-panel toolbar-chip-row-panel-compact'
} else {
data.toolbarChipRowClass = 'toolbar-chip-row toolbar-chip-row-panel'
}
if (data.activeSheet == 'title' || data.activeSheet == 'align' || data.activeSheet == 'more' || data.activeSheet == 'line-height' || data.activeSheet == 'letter-spacing' || data.activeSheet == 'font-size' || data.activeSheet == 'font-family' || data.activeSheet == 'block-indent' || data.activeSheet == 'list') {
data.toolbarChipCellClass = 'toolbar-chip-cell toolbar-chip-cell-full'
return
}
if (data.activeSheet == 'style') {
data.toolbarChipCellClass = 'toolbar-chip-cell toolbar-chip-cell-third'
return
}
data.toolbarChipCellClass = 'toolbar-chip-cell toolbar-chip-cell-quarter'
}
function getSheetHostStyle() : string {
// #ifndef WEB
return ''
// #endif
if (isWebPhoneLayout()) {
syncToolbarChipClasses()
return ''
}
syncToolbarChipClasses()
return `left:${data.sheetLeft}px;top:${data.sheetTop}px;width:${getSheetWidth()}px;`
}
function getSheetHostClass() : string {
// #ifdef WEB
if (isWebPhoneLayout()) {
return 'sheet-host sheet-host-mobile-web'
}
return 'sheet-host sheet-host-floating'
// #endif
return 'sheet-host'
}
function getToolbarPanelArrowStyle() : string {
return `left:${data.sheetArrowLeft}px;`
}
function isCompactToolbar() : boolean {
const windowWidth = data.pageWindowWidth > 0 ? data.pageWindowWidth : uni.getWindowInfo().windowWidth
return windowWidth < 1240
}
function isDenseToolbar() : boolean {
const windowWidth = data.pageWindowWidth > 0 ? data.pageWindowWidth : uni.getWindowInfo().windowWidth
return windowWidth < 980
}
function isStyleSheetHighlighted() : boolean {
return data.activeSheet == 'style' || hasInlineStyleActive()
}
function getDesktopFontFamilyLabel() : string {
if (data.formats.fontFamily == 'Microsoft YaHei') {
return '雅黑'
}
if (data.formats.fontFamily == 'SimHei') {
return '黑体'
}
if (data.formats.fontFamily == 'SimSun') {
return '宋体'
}
if (data.formats.fontFamily == 'KaiTi') {
return '楷体'
}
if (data.formats.fontFamily == 'FangSong') {
return '仿宋'
}
if (data.formats.fontFamily == 'Georgia') {
return 'Geo'
}
if (data.formats.fontFamily == 'Times New Roman') {
return 'Times'
}
if (data.formats.fontFamily == 'Courier New') {
return 'Mono'
}
return '字体'
}
function getDesktopToolbarShellClass() : string {
let className = 'toolbar-shell'
if (isCompactToolbar()) {
className += ' toolbar-shell-compact'
}
if (isDenseToolbar()) {
className += ' toolbar-shell-dense'
}
return className
}
function getDesktopToolbarGroupClass(isLeading: boolean) : string {
let className = 'toolbar-group'
if (isLeading) {
className += ' toolbar-group-leading'
}
if (isCompactToolbar()) {
className += ' toolbar-group-compact'
if (isLeading) {
className += ' toolbar-group-leading-compact'
}
}
if (isDenseToolbar()) {
className += ' toolbar-group-dense'
}
return className
}
function getDesktopToolbarDividerClass() : string {
if (isCompactToolbar()) {
return 'toolbar-divider toolbar-divider-hidden'
}
return 'toolbar-divider'
}
function getToolbarPanelClass() : string {
// #ifndef WEB
return 'toolbar-panel toolbar-panel-attached'
// #endif
if (isWebPhoneLayout()) {
return 'toolbar-panel toolbar-panel-mobile-web'
}
if (data.sheetAnimated) {
return 'toolbar-panel toolbar-panel-attached toolbar-panel-floating toolbar-panel-floating-attached toolbar-panel-floating-entered'
}
return 'toolbar-panel toolbar-panel-attached toolbar-panel-floating toolbar-panel-floating-attached'
}
function getToolbarPanelHeaderClass() : string {
// #ifndef WEB
return 'toolbar-panel-header'
// #endif
if (isWebPhoneLayout()) {
return 'toolbar-panel-header toolbar-panel-header-mobile-web'
}
return 'toolbar-panel-header toolbar-panel-header-floating'
}
function getToolbarPanelTitleClass() : string {
// #ifndef WEB
return 'toolbar-panel-title'
// #endif
if (isWebPhoneLayout()) {
return 'toolbar-panel-title toolbar-panel-title-mobile-web'
}
return 'toolbar-panel-title toolbar-panel-title-floating'
}
function getToolbarPanelSubtitleClass() : string {
// #ifndef WEB
return 'toolbar-panel-subtitle'
// #endif
if (isWebPhoneLayout()) {
return 'toolbar-panel-subtitle toolbar-panel-subtitle-mobile-web'
}
return 'toolbar-panel-subtitle toolbar-panel-subtitle-floating'
}
function getToolbarPanelScrollClass() : string {
// #ifndef WEB
return 'toolbar-panel-scroll'
// #endif
if (isWebPhoneLayout()) {
return 'toolbar-panel-scroll toolbar-panel-scroll-mobile-web'
}
return 'toolbar-panel-scroll toolbar-panel-scroll-floating'
}
function getDesktopMenuItemClass(active: boolean) : string {
return active ? 'desktop-menu-item desktop-menu-item-active' : 'desktop-menu-item'
}
function getDesktopMenuBadgeClass(active: boolean) : string {
return active ? 'desktop-menu-badge desktop-menu-badge-active' : 'desktop-menu-badge'
}
function getDesktopMenuTextClass(active: boolean) : string {
return active ? 'desktop-menu-text desktop-menu-text-active' : 'desktop-menu-text'
}
function closeSheets() {
data.sheetAnimated = false
data.editorInputType = ''
// #ifdef WEB
hideToolbarTooltip()
// #endif
data.activeSheet = ''
syncToolbarChipClasses()
syncToolbarBottom()
syncToolbarVisibility()
}
function getClearColorSwatchClass(isActive: boolean) : string {
let className = 'color-swatch color-swatch-panel'
className += ' color-swatch-clear'
if (isActive) {
className += ' color-swatch-active'
}
return className
}
function getFilledColorSwatchClass(isActive: boolean) : string {
let className = 'color-swatch color-swatch-panel'
if (isActive) {
className += ' color-swatch-active'
}
return className
}
function refocusEditorAfterToolbarAction() {
// #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
setTimeout(() => {
const editorElement = uni.getElementById('editor')
if (editorElement != null) {
editorElement.focus()
}
}, 30)
// #endif
}
function updatePageWindowHeight() {
const windowInfo = uni.getWindowInfo()
data.pageWindowHeight = windowInfo.windowHeight
data.pageWindowWidth = windowInfo.windowWidth
syncToolbarChipClasses()
}
function getSheetButtonId(sheet: string) : string {
if (sheet == 'more') {
return 'toolbar-button-align'
}
if (sheet == 'title') {
return 'toolbar-button-title'
}
if (sheet == 'style') {
return 'toolbar-button-style'
}
if (sheet == 'font-size') {
return 'toolbar-button-font-size'
}
if (sheet == 'font-family') {
return 'toolbar-button-font-family'
}
if (sheet == 'text-color') {
return 'toolbar-button-text-color'
}
if (sheet == 'background-color') {
return 'toolbar-button-background-color'
}
if (sheet == 'line-height') {
return 'toolbar-button-line-height'
}
if (sheet == 'letter-spacing') {
return 'toolbar-button-letter-spacing'
}
if (sheet == 'block-indent') {
return 'toolbar-button-block-indent'
}
if (sheet == 'list') {
return 'toolbar-button-list'
}
return 'toolbar-button-align'
}
function updateSheetAnchor(sheet: string) {
updatePageWindowHeight()
// #ifndef WEB
data.sheetTop = 74
data.sheetLeft = 12
data.sheetArrowLeft = 32
return
// #endif
const layoutElement = uni.getElementById('editor-layout')
const buttonElement = uni.getElementById(getSheetButtonId(sheet))
if (buttonElement == null || layoutElement == null) {
data.sheetTop = 74
data.sheetLeft = 12
data.sheetArrowLeft = 32
return
}
const pageRect = layoutElement!.getBoundingClientRect()
const rect = buttonElement!.getBoundingClientRect()
const panelWidth = getSheetWidth()
let panelLeft = rect.left - pageRect.left + rect.width / 2 - panelWidth / 2
let maxLeft = pageRect.width - panelWidth - 12
if (maxLeft < 12) {
maxLeft = 12
}
if (panelLeft < 12) {
panelLeft = 12
}
if (panelLeft > maxLeft) {
panelLeft = maxLeft
}
let arrowLeft = rect.left - pageRect.left + rect.width / 2 - panelLeft - 8
if (arrowLeft < 18) {
arrowLeft = 18
}
if (arrowLeft > panelWidth - 30) {
arrowLeft = panelWidth - 30
}
data.sheetTop = rect.bottom - pageRect.top + 12
data.sheetLeft = panelLeft
data.sheetArrowLeft = arrowLeft
}
function onEditorReady() {
const options: CreateEditorContextAsyncOptions = {
id: 'editor',
success: (context: EditorContext) => {
editorContext.value = context
data.editorReadyTest = true
},
fail: (error: UniError) => {
console.log('createEditorContextAsync fail', error)
}
}
uni.createEditorContextAsync(options)
}
function applyFormat(name: string, value: string | number | null | undefined) {
const editor = getEditor()
if (editor == null) {
return
}
// #ifndef WEB
statusSyncLockedUntil = Date.now() + 400
// #endif
editor.format(name, value)
editor.scrollIntoView(null)
}
function setLocalFormat(name: string, value: string | number | boolean | null) {
if (name == 'bold') {
data.formats.bold = value == true
return
}
if (name == 'italic') {
data.formats.italic = value == true
return
}
if (name == 'underline') {
data.formats.underline = value == true
return
}
if (name == 'strike') {
data.formats.strike = value == true
return
}
if (name == 'blockquote') {
data.formats.blockquote = value == true
return
}
if (name == 'header') {
data.formats.header = value != null ? value as number : 0
return
}
if (name == 'code-block') {
data.formats.codeBlock = value == true
return
}
if (name == 'link') {
data.formats.link = value != null ? value as string : ''
return
}
if (name == 'list') {
data.formats.list = value != null ? value as string : ''
return
}
if (name == 'align') {
data.formats.align = value != null ? value as string : ''
return
}
if (name == 'textIndent') {
data.formats.textIndent = value != null ? value as string : ''
return
}
if (name == 'marginLeft') {
data.formats.marginLeft = value != null ? value as string : ''
return
}
if (name == 'marginRight') {
data.formats.marginRight = value != null ? value as string : ''
return
}
if (name == 'lineHeight') {
data.formats.lineHeight = value != null ? value as string : ''
return
}
if (name == 'letterSpacing') {
data.formats.letterSpacing = value != null ? value as string : ''
return
}
if (name == 'fontFamily') {
data.formats.fontFamily = value != null ? value as string : ''
return
}
if (name == 'fontSize') {
data.formats.fontSize = value != null ? value as string : ''
return
}
if (name == 'color') {
data.formats.color = value != null ? value as string : ''
return
}
if (name == 'backgroundColor') {
data.formats.backgroundColor = value != null ? value as string : ''
}
}
function applyFormatWithState(name: string, value: string | number | null | undefined) {
setLocalFormat(name, value)
applyFormat(name, value)
}
function setToolbarFormatsForTest(header: number, list: string) {
setLocalFormat('header', header)
setLocalFormat('list', list)
}
function applyToolbarPresetForTest(preset: string) {
if (preset == 'title-h2') {
setToolbarFormatsForTest(2, '')
return
}
if (preset == 'title-h1') {
setToolbarFormatsForTest(1, '')
return
}
if (preset == 'list-bullet') {
setToolbarFormatsForTest(0, 'bullet')
return
}
if (preset == 'list-ordered') {
setToolbarFormatsForTest(0, 'ordered')
return
}
if (preset == 'list-unchecked') {
setToolbarFormatsForTest(0, 'unchecked')
return
}
if (preset == 'list-none') {
setToolbarFormatsForTest(0, '')
}
}
function toggleInlineFormat(name: string) {
if (name == 'bold') {
setLocalFormat('bold', !data.formats.bold)
applyFormat('bold', null)
return
}
if (name == 'italic') {
setLocalFormat('italic', !data.formats.italic)
applyFormat('italic', null)
return
}
if (name == 'underline') {
setLocalFormat('underline', !data.formats.underline)
applyFormat('underline', null)
return
}
if (name == 'strike') {
setLocalFormat('strike', !data.formats.strike)
applyFormat('strike', null)
}
}
function cloneFormats(source: Formats) : Formats {
return {
bold: source.bold,
italic: source.italic,
underline: source.underline,
strike: source.strike,
blockquote: source.blockquote,
link: source.link,
header: source.header,
list: source.list,
align: source.align,
textIndent: source.textIndent,
marginLeft: source.marginLeft,
marginRight: source.marginRight,
lineHeight: source.lineHeight,
letterSpacing: source.letterSpacing,
fontFamily: source.fontFamily,
fontSize: source.fontSize,
color: source.color,
backgroundColor: source.backgroundColor,
codeBlock: source.codeBlock
}
}
function clearFormatPainterState() {
data.formatPainterActive = false
}
function resetLocalFormats() {
assignFormats(data.formats, createDefaultFormats())
}
function resetFormatsForTest() {
assignFormats(data.formats, createDefaultFormats())
}
function applyCopiedFormats() {
const copied = cloneFormats(data.formatPainterFormats)
applyFormat('header', copied.header > 0 ? copied.header : 0)
applyFormat('list', copied.list != '' ? copied.list : null)
applyFormat('align', copied.align != '' ? copied.align : null)
applyFormat('textIndent', copied.textIndent != '' ? copied.textIndent : null)
applyFormat('marginLeft', copied.marginLeft != '' ? copied.marginLeft : null)
applyFormat('marginRight', copied.marginRight != '' ? copied.marginRight : null)
applyFormat('lineHeight', copied.lineHeight != '' ? copied.lineHeight : null)
applyFormat('letterSpacing', copied.letterSpacing != '' ? copied.letterSpacing : null)
applyFormat('fontFamily', copied.fontFamily != '' ? copied.fontFamily : null)
applyFormat('fontSize', copied.fontSize != '' ? copied.fontSize : null)
applyFormat('color', copied.color != '' ? copied.color : null)
applyFormat('backgroundColor', copied.backgroundColor != '' ? copied.backgroundColor : null)
if (copied.bold != data.formats.bold) {
applyFormat('bold', null)
}
if (copied.italic != data.formats.italic) {
applyFormat('italic', null)
}
if (copied.underline != data.formats.underline) {
applyFormat('underline', null)
}
if (copied.strike != data.formats.strike) {
applyFormat('strike', null)
}
}
function toggleFormatPainter() {
if (data.formatPainterActive) {
clearFormatPainterState()
return
}
data.formatPainterFormats = cloneFormats(data.formats)
data.formatPainterActive = true
closeSheets()
}
function getEditorBottomSpacing() : number {
return 0
}
function getEditorStageStyle() : string {
const paddingBottom = getEditorBottomSpacing()
return `padding-bottom:${paddingBottom}px;`
}
function hideKeyboardForSheet() {
// editor 切换 hidekeyboard: 下边注释放开
// uni.hideKeyboard()
data.editorInputType = 'none'
data.isFocused = false
uni.hideKeyboard()
}
function toggleSheet(sheet: string) {
if (data.activeSheet == sheet) {
closeSheets()
return
}
// #ifdef APP-ANDROID
uni.showToast({
title: '请手动收起键盘后选择操作',
icon: 'none'
})
// #endif
updateSheetAnchor(sheet)
data.activeSheet = sheet
syncToolbarChipClasses()
data.sheetAnimated = false
setTimeout(() => {
if (data.activeSheet == sheet) {
data.sheetAnimated = true
}
}, 16)
// #ifndef APP-ANDROID
hideKeyboardForSheet()
// #endif
syncToolbarBottom()
syncToolbarVisibility()
}
function openMoreSheet() {
toggleSheet('more')
}
function openTitleSheet() {
toggleSheet('title')
}
function openStyleSheet() {
toggleSheet('style')
}
function openTextColorSheet() {
toggleSheet('text-color')
}
function openBackgroundColorSheet() {
toggleSheet('background-color')
}
function openLineHeightSheet() {
toggleSheet('line-height')
}
function openLetterSpacingSheet() {
toggleSheet('letter-spacing')
}
function openBlockIndentSheet() {
toggleSheet('block-indent')
}
function openListSheet() {
toggleSheet('list')
}
function openFontSizeSheet() {
toggleSheet('font-size')
}
function openFontFamilySheet() {
toggleSheet('font-family')
}
function openAlignSheet() {
toggleSheet('align')
}
function getPanelTitle() : string {
if (data.activeSheet == 'title') {
return '设置标题'
}
if (data.activeSheet == 'style') {
return '设置字格式'
}
if (data.activeSheet == 'text-color') {
return '设置文字颜色'
}
if (data.activeSheet == 'background-color') {
return '设置背景颜色'
}
if (data.activeSheet == 'line-height') {
return '设置行间距'
}
if (data.activeSheet == 'letter-spacing') {
return '设置字间距'
}
if (data.activeSheet == 'font-size') {
return '设置字号'
}
if (data.activeSheet == 'font-family') {
return '设置字体'
}
if (data.activeSheet == 'block-indent') {
return '设置两端缩进'
}
if (data.activeSheet == 'align') {
return '对齐方式'
}
if (data.activeSheet == 'list') {
return '设置列表'
}
return '更多操作'
}
function getTitleSummary() : string {
if (data.formats.codeBlock) {
return '当前为代码块'
}
if (data.formats.blockquote) {
return '当前为引用'
}
if (data.formats.header > 0) {
return `当前为大标题${data.formats.header}`
}
return '当前为正文'
}
function openLinkModal() {
const editor = getEditor()
if (editor == null) {
return
}
closeSheets()
data.linkSelectionText = ''
data.linkOriginalHref = data.formats.link
// #ifndef WEB
uni.hideKeyboard()
// #endif
data.linkDraftText = ''
data.linkDraftHref = data.formats.link
data.linkModalVisible = true
data.linkUseSelectionText = false
clearToolbarHideTimer()
const options: UniEditorElementGetSelectionTextOptions = {
success: (res: UniEditorElementGetSelectionTextOptionsRes) => {
const selectionText = res.text.trim()
if (selectionText == '') {
return
}
data.linkSelectionText = selectionText
data.linkDraftText = selectionText
data.linkUseSelectionText = data.formats.link == ''
},
fail: () => {},
complete: () => {}
}
editor.getSelectionText(options)
}
function closeLinkModal() {
data.linkModalVisible = false
data.linkUseSelectionText = false
data.linkDraftText = ''
data.linkDraftHref = ''
data.linkSelectionText = ''
data.linkOriginalHref = ''
}
function getDesktopTitleLabel() : string {
if (data.formats.header > 0) {
return `标题 ${data.formats.header}`
}
return '标题'
}
function getDesktopTitleMiniLabel() : string {
if (data.formats.header > 0) {
return `${data.formats.header}`
}
return 'P'
}
function getDesktopTitleIconGlyph() : string {
if (data.formats.header == 1) {
return ""
}
if (data.formats.header == 2) {
return ""
}
if (data.formats.header == 3) {
return ""
}
if (data.formats.header == 4) {
return ""
}
if (data.formats.header == 5) {
return ""
}
if (data.formats.header == 6) {
return ""
}
return ""
}
function getDesktopAlignIconGlyph() : string {
if (data.formats.align == 'center') {
return "\ue6b6"
}
if (data.formats.align == 'right') {
return "\ue6a6"
}
if (data.formats.align == 'justify') {
return "\ue6a8"
}
return "\ue6b7"
}
function getStyleSummary() : string {
const names: string[] = []
if (data.formats.bold) {
names.push('加粗')
}
if (data.formats.italic) {
names.push('斜体')
}
if (data.formats.underline) {
names.push('下划线')
}
if (data.formats.strike) {
names.push('删除线')
}
if (data.formats.lineHeight != '') {
names.push(`行间距 ${data.formats.lineHeight}`)
}
if (data.formats.letterSpacing != '') {
names.push(`字间距 ${data.formats.letterSpacing}`)
}
if (data.formats.fontSize != '') {
names.push(`字号 ${data.formats.fontSize}`)
}
if (data.formats.fontFamily != '') {
names.push(`字体 ${getDesktopFontFamilyLabel()}`)
}
if (names.length == 0) {
return '当前未设置字格式'
}
return names.join(' / ')
}
function getAlignSummary() : string {
if (data.formats.align == '') {
return '当前为默认对齐'
}
if (data.formats.align == 'center') {
return '当前为居中'
}
if (data.formats.align == 'right') {
return '当前为居右'
}
if (data.formats.align == 'justify') {
return '当前为两端对齐'
}
return '当前为居左'
}
function getDesktopAlignLabel() : string {
if (data.formats.align == 'center') {
return '居中'
}
if (data.formats.align == 'right') {
return '居右'
}
if (data.formats.align == 'justify') {
return '两端'
}
return '居左'
}
function getDesktopBlockIndentLabel() : string {
const margin = data.formats.marginLeft
if (margin != '' && margin == data.formats.marginRight) {
return margin.replace('px', '')
}
return '0'
}
function getDesktopListLabel() : string {
if (data.formats.list == 'ordered') {
return '有序列表'
}
if (data.formats.list == 'bullet') {
return '无序列表'
}
return '列表'
}
function getDesktopLineHeightLabel() : string {
if (data.formats.lineHeight == '1.5') {
return '1.5'
}
if (data.formats.lineHeight == '1.8') {
return '1.8'
}
if (data.formats.lineHeight == '2') {
return '2.0'
}
return '行间距'
}
function getDesktopLineHeightMiniLabel() : string {
if (data.formats.lineHeight == '1.5') {
return '1.5'
}
if (data.formats.lineHeight == '1.8') {
return '1.8'
}
if (data.formats.lineHeight == '2') {
return '2'
}
return 'D'
}
function getDesktopLetterSpacingLabel() : string {
if (data.formats.letterSpacing == '0px') {
return '0px'
}
if (data.formats.letterSpacing == '1px') {
return '1px'
}
if (data.formats.letterSpacing == '2px') {
return '2px'
}
return '字间距'
}
function getDesktopLetterSpacingMiniLabel() : string {
if (data.formats.letterSpacing == '0px') {
return '0'
}
if (data.formats.letterSpacing == '1px') {
return '1'
}
if (data.formats.letterSpacing == '2px') {
return '2'
}
return 'D'
}
function getDesktopFontSizeLabel() : string {
if (data.formats.fontSize != '') {
return data.formats.fontSize
}
return '17px'
}
function getToolbarTextColorChipStyle() : string {
const color = data.formats.color != '' ? data.formats.color : '#111827'
return `background-color:${color};`
}
function getToolbarBackgroundColorChipStyle() : string {
const color = data.formats.backgroundColor != '' ? data.formats.backgroundColor : '#f3f4f6'
return `background-color:${color};`
}
function getPanelSubtitle() : string {
if (data.activeSheet == 'title') {
return getTitleSummary()
}
if (data.activeSheet == 'style') {
return getStyleSummary()
}
if (data.activeSheet == 'text-color') {
if (data.formats.color == '') {
return '当前使用默认文字颜色'
}
return `当前文字颜色 ${data.formats.color}`
}
if (data.activeSheet == 'background-color') {
if (data.formats.backgroundColor == '') {
return '当前未设置文字背景颜色'
}
return `当前背景颜色 ${data.formats.backgroundColor}`
}
if (data.activeSheet == 'line-height') {
if (data.formats.lineHeight == '') {
return '当前使用默认行间距'
}
return `当前行间距 ${data.formats.lineHeight}`
}
if (data.activeSheet == 'letter-spacing') {
if (data.formats.letterSpacing == '') {
return '当前使用默认字间距'
}
return `当前字间距 ${data.formats.letterSpacing}`
}
if (data.activeSheet == 'font-size') {
if (data.formats.fontSize == '') {
return '当前使用默认字号 17px'
}
return `当前字号 ${data.formats.fontSize}`
}
if (data.activeSheet == 'font-family') {
if (data.formats.fontFamily == '') {
return '当前使用默认字体'
}
return `当前字体 ${getDesktopFontFamilyLabel()}`
}
if (data.activeSheet == 'block-indent') {
if (data.formats.marginLeft == '' || data.formats.marginLeft != data.formats.marginRight) {
return '当前未设置两端缩进'
}
return `当前两端缩进 ${data.formats.marginLeft}`
}
if (data.activeSheet == 'align') {
return getAlignSummary()
}
if (data.activeSheet == 'list') {
return data.formats.list == '' ? '当前未设置列表' : `当前列表 ${getDesktopListLabel()}`
}
return '插入与编辑快捷操作'
}
function hasTitleFormatActive() : boolean {
return data.formats.header > 0 || data.formats.blockquote || data.formats.codeBlock
}
function isTitleSheetHighlighted() : boolean {
return data.activeSheet == 'title' || hasTitleFormatActive()
}
function isAlignSheetHighlighted() : boolean {
return data.activeSheet == 'align' || data.formats.align != ''
}
function isLineHeightSheetHighlighted() : boolean {
return data.activeSheet == 'line-height' || data.formats.lineHeight != ''
}
function isLetterSpacingSheetHighlighted() : boolean {
return data.activeSheet == 'letter-spacing' || data.formats.letterSpacing != ''
}
function isFontSizeSheetHighlighted() : boolean {
return data.activeSheet == 'font-size' || data.formats.fontSize != ''
}
function isFontFamilySheetHighlighted() : boolean {
return data.activeSheet == 'font-family' || data.formats.fontFamily != ''
}
function isTextIndentHighlighted() : boolean {
return data.formats.textIndent != ''
}
function isBlockIndentSheetHighlighted() : boolean {
return data.activeSheet == 'block-indent' || (data.formats.marginLeft != '' && data.formats.marginLeft == data.formats.marginRight)
}
function isListSheetHighlighted() : boolean {
return data.activeSheet == 'list' || data.formats.list == 'ordered' || data.formats.list == 'bullet'
}
function isBlockIndentValue(value: string) : boolean {
if (value == '0px') {
return data.formats.marginLeft == '' && data.formats.marginRight == ''
}
return data.formats.marginLeft == value && data.formats.marginRight == value
}
function isTextColorSheetHighlighted() : boolean {
return data.activeSheet == 'text-color' || data.formats.color != ''
}
function isBackgroundColorSheetHighlighted() : boolean {
return data.activeSheet == 'background-color' || data.formats.backgroundColor != ''
}
function isParagraphActive() : boolean {
return data.formats.header == 0 && data.formats.list == '' && !data.formats.blockquote && !data.formats.codeBlock
}
const pageShellClass = computed((): string => getPageShellClass())
const editorLayoutClass = computed((): string => getEditorLayoutClass())
const toolbarWrapStyle = computed((): string => getToolbarWrapStyle())
const desktopToolbarShellClass = computed((): string => getDesktopToolbarShellClass())
const desktopToolbarLeadingGroupClass = computed((): string => getDesktopToolbarGroupClass(true))
const desktopToolbarGroupClass = computed((): string => getDesktopToolbarGroupClass(false))
const desktopToolbarDividerClass = computed((): string => getDesktopToolbarDividerClass())
const fontSizeSheetHighlighted = computed((): boolean => isFontSizeSheetHighlighted())
const fontFamilySheetHighlighted = computed((): boolean => isFontFamilySheetHighlighted())
const textColorSheetHighlighted = computed((): boolean => isTextColorSheetHighlighted())
const backgroundColorSheetHighlighted = computed((): boolean => isBackgroundColorSheetHighlighted())
const alignSheetHighlighted = computed((): boolean => isAlignSheetHighlighted())
const desktopAlignIconGlyph = computed((): string => getDesktopAlignIconGlyph())
const textIndentHighlighted = computed((): boolean => isTextIndentHighlighted())
const blockIndentSheetHighlighted = computed((): boolean => isBlockIndentSheetHighlighted())
const lineHeightSheetHighlighted = computed((): boolean => isLineHeightSheetHighlighted())
const letterSpacingSheetHighlighted = computed((): boolean => isLetterSpacingSheetHighlighted())
const listSheetHighlighted = computed((): boolean => isListSheetHighlighted())
const styleSheetHighlighted = computed((): boolean => isStyleSheetHighlighted())
const titleSheetHighlighted = computed((): boolean => isTitleSheetHighlighted())
const toolbarTextColorChipStyle = computed((): string => getToolbarTextColorChipStyle())
const toolbarBackgroundColorChipStyle = computed((): string => getToolbarBackgroundColorChipStyle())
const toolbarTooltipStyle = computed((): string => getToolbarTooltipStyle())
const sheetHostClass = computed((): string => getSheetHostClass())
const sheetHostStyle = computed((): string => getSheetHostStyle())
const sheetPanelStyle = computed((): string => getSheetPanelStyle())
const toolbarPanelArrowStyle = computed((): string => getToolbarPanelArrowStyle())
const toolbarPanelClass = computed((): string => getToolbarPanelClass())
const toolbarPanelHeaderClass = computed((): string => getToolbarPanelHeaderClass())
const toolbarPanelTitleClass = computed((): string => getToolbarPanelTitleClass())
const toolbarPanelSubtitleClass = computed((): string => getToolbarPanelSubtitleClass())
const toolbarPanelScrollClass = computed((): string => getToolbarPanelScrollClass())
const paragraphActive = computed((): boolean => isParagraphActive())
const editorStageStyle = computed((): string => getEditorStageStyle())
const desktopMenuItemClass = computed((): string => getDesktopMenuItemClass(false))
const desktopMenuItemActiveClass = computed((): string => getDesktopMenuItemClass(true))
const desktopMenuBadgeClass = computed((): string => getDesktopMenuBadgeClass(false))
const desktopMenuBadgeActiveClass = computed((): string => getDesktopMenuBadgeClass(true))
const desktopMenuTextClass = computed((): string => getDesktopMenuTextClass(false))
const desktopMenuTextActiveClass = computed((): string => getDesktopMenuTextClass(true))
const clearColorSwatchClass = computed((): string => getClearColorSwatchClass(false))
const clearColorSwatchActiveClass = computed((): string => getClearColorSwatchClass(true))
const filledColorSwatchClass = computed((): string => getFilledColorSwatchClass(false))
const filledColorSwatchActiveClass = computed((): string => getFilledColorSwatchClass(true))
const webPhoneLayout = computed((): boolean => isWebPhoneLayout())
function clearTitleFormats() {
applyFormat('blockquote', null)
if (data.formats.codeBlock) {
setLocalFormat('code-block', false)
applyFormat('code-block', null)
}
applyFormatWithState('list', null)
}
function setParagraph() {
clearTitleFormats()
applyFormatWithState('header', 0)
closeSheets()
refocusEditorAfterToolbarAction()
}
function setHeadingLevel(level: number) {
clearTitleFormats()
if (data.formats.header == level) {
applyFormatWithState('header', 0)
} else {
applyFormatWithState('header', level)
}
closeSheets()
refocusEditorAfterToolbarAction()
}
function setOrderedList() {
applyFormat('blockquote', null)
if (data.formats.codeBlock) {
setLocalFormat('code-block', false)
applyFormat('code-block', null)
}
applyFormatWithState('header', 0)
applyFormatWithState('list', data.formats.list == 'ordered' ? null : 'ordered')
closeSheets()
refocusEditorAfterToolbarAction()
}
function setBulletList() {
applyFormat('blockquote', null)
if (data.formats.codeBlock) {
setLocalFormat('code-block', false)
applyFormat('code-block', null)
}
applyFormatWithState('header', 0)
applyFormatWithState('list', data.formats.list == 'bullet' ? null : 'bullet')
closeSheets()
refocusEditorAfterToolbarAction()
}
function clearListFormat() {
applyFormatWithState('list', null)
closeSheets()
refocusEditorAfterToolbarAction()
}
function toggleBlockquote() {
const nextValue = !data.formats.blockquote
const isListFormat = data.formats.list != ''
const isHeadFormat = data.formats.header != 0
const isCodeBlock = data.formats.codeBlock
if (nextValue && isListFormat) {
applyFormatWithState('list', null)
}
if (nextValue && isHeadFormat) {
applyFormatWithState('header', 0)
}
if (nextValue && isCodeBlock) {
setLocalFormat('code-block', false)
applyFormat('code-block', null)
}
setLocalFormat('blockquote', nextValue)
applyFormat('blockquote', null)
closeSheets()
refocusEditorAfterToolbarAction()
}
function toggleCodeBlock() {
const nextValue = !data.formats.codeBlock
if (nextValue) {
if (data.formats.list != '') {
applyFormatWithState('list', null)
}
if (data.formats.header != 0) {
applyFormatWithState('header', 0)
}
if (data.formats.blockquote) {
applyFormatWithState('blockquote', null)
}
}
setLocalFormat('code-block', nextValue)
applyFormat('code-block', null)
closeSheets()
refocusEditorAfterToolbarAction()
}
function toggleBold() {
toggleInlineFormat('bold')
}
function toggleItalic() {
toggleInlineFormat('italic')
}
function toggleUnderline() {
toggleInlineFormat('underline')
}
function toggleStrike() {
toggleInlineFormat('strike')
}
function toggleBoldAndClose() {
toggleBold()
closeSheets()
refocusEditorAfterToolbarAction()
}
function toggleItalicAndClose() {
toggleItalic()
closeSheets()
refocusEditorAfterToolbarAction()
}
function toggleUnderlineAndClose() {
toggleUnderline()
closeSheets()
refocusEditorAfterToolbarAction()
}
function toggleStrikeAndClose() {
toggleStrike()
closeSheets()
refocusEditorAfterToolbarAction()
}
function setTextColor(color: string) {
applyFormatWithState('color', color == '' ? null : color)
}
function setTextColorAndClose(color: string) {
setTextColor(color)
closeSheets()
refocusEditorAfterToolbarAction()
}
function setBackgroundColor(color: string) {
applyFormatWithState('backgroundColor', color == '' ? null : color)
}
function setBackgroundColorAndClose(color: string) {
setBackgroundColor(color)
closeSheets()
refocusEditorAfterToolbarAction()
}
function setLineHeight(value: string) {
applyFormatWithState('lineHeight', value == '' ? null : value)
}
function setLineHeightAndClose(value: string) {
setLineHeight(value)
closeSheets()
refocusEditorAfterToolbarAction()
}
function setLetterSpacing(value: string) {
applyFormatWithState('letterSpacing', value == '' ? null : value)
}
function setLetterSpacingAndClose(value: string) {
setLetterSpacing(value)
closeSheets()
refocusEditorAfterToolbarAction()
}
function setFontSize(value: string) {
applyFormatWithState('fontSize', value == '' ? null : value)
}
function setFontSizeAndClose(value: string) {
setFontSize(value)
closeSheets()
refocusEditorAfterToolbarAction()
}
function setFontFamily(value: string) {
applyFormatWithState('fontFamily', value == '' ? null : value)
}
function setFontFamilyAndClose(value: string) {
setFontFamily(value)
closeSheets()
refocusEditorAfterToolbarAction()
}
function toggleTextIndent() {
const nextValue = data.formats.textIndent == '' ? '2em' : ''
applyFormatWithState('textIndent', nextValue == '' ? null : nextValue)
refocusEditorAfterToolbarAction()
}
function setBlockIndent(value: string) {
const indentValue = value == '0px' ? '' : value
applyFormatWithState('marginLeft', indentValue == '' ? null : indentValue)
applyFormatWithState('marginRight', indentValue == '' ? null : indentValue)
}
function setBlockIndentAndClose(value: string) {
setBlockIndent(value)
closeSheets()
refocusEditorAfterToolbarAction()
}
function setAlignLeft() {
applyFormatWithState('align', 'left')
closeSheets()
refocusEditorAfterToolbarAction()
}
function setAlignCenter() {
applyFormatWithState('align', 'center')
closeSheets()
refocusEditorAfterToolbarAction()
}
function setAlignRight() {
applyFormatWithState('align', 'right')
closeSheets()
refocusEditorAfterToolbarAction()
}
function setAlignJustify() {
applyFormatWithState('align', 'justify')
closeSheets()
refocusEditorAfterToolbarAction()
}
function clearAlign() {
applyFormatWithState('align', null)
closeSheets()
refocusEditorAfterToolbarAction()
}
function clearFormatting() {
const editor = getEditor()
if (editor == null) {
return
}
editor.removeFormat(null)
resetLocalFormats()
clearFormatPainterState()
closeSheets()
refocusEditorAfterToolbarAction()
}
function toggleChecklistTool() {
applyFormatWithState('list', data.formats.list == 'check' ? null : 'check')
closeSheets()
refocusEditorAfterToolbarAction()
}
function insertDivider() {
const editor = getEditor()
if (editor == null) {
return
}
editor.insertDivider(null)
closeSheets()
refocusEditorAfterToolbarAction()
}
function getCon () {
const editor = getEditor()
if (editor == null) {
return
}
editor.getContents({
success: (res: UniEditorElementGetContentsOptionsRes) => {
console.log('文本详情:', res)
data.getContentDeltaTest = res.delta
},
fail: (err: any) => {
console.log(err)
}
})
}
// 自动化测试专用
const setContents = (options: any) => {
const editor = getEditor()
if (editor == null) {
return
}
editor.setContents({
delta: {
ops: options
},
success: (res: any) => {
console.log('setContents-success', res)
},
fail: (err: any) => {
console.log(err)
}
})
}
const blur = () => {
const editor = getEditor()
if (editor == null) {
return
}
editor.blur({
success: (res: any) => {
console.log('编辑器失焦:', res)
data.blurTest = true
},
fail: (err: any) => {
console.log(err)
data.blurTest = false
}
})
}
const clear = () => {
const editor = getEditor()
if (editor == null) {
return
}
editor.clear({
success: function (res: any) {
console.log("clear success",res)
data.clearTest = true
},
fail: (err: any) => {
console.log(err)
data.clearTest = false
}
})
}
const clearShowModal = () => {
uni.showModal({
title: '清空编辑器',
content: '确定清空编辑器全部内容?',
success: (res) => {
if (res.confirm) {
clear()
}
}
})
}
const removeFormat = () => {
const editor = getEditor()
if (editor == null) {
return
}
editor.removeFormat(null)
data.removeFormatTest = true
}
function insertImage(path: string) {
const editor = getEditor()
if (editor == null) {
return
}
const options: UniEditorElementInsertImageOptions = {
src: path,
alt: 'image',
success: () => {
data.insertImageTest = true
},
fail: () => {
data.insertImageTest = false
}
}
editor.insertImage(options)
closeSheets()
refocusEditorAfterToolbarAction()
}
function normalizeLinkHref(value: string) : string {
const trimmedValue = value.trim()
if (trimmedValue == '') {
return ''
}
if (trimmedValue.startsWith('http://') || trimmedValue.startsWith('https://') || trimmedValue.startsWith('mailto:') || trimmedValue.startsWith('tel:')) {
return trimmedValue
}
return `https://${trimmedValue}`
}
function escapeRegExp(value: string) : string {
return value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\<!-- UTSCOMJSON.editor.example -->')
}
function getBooleanAttribute(attributes: UTSJSONObject, key: string) : boolean {
const value = attributes[key]
return value != null && (value as boolean)
}
function buildOpAttributes(attributes: UTSJSONObject | null, nextLinkValue: string | null, preserveLink: boolean) : UTSJSONObject | null {
let hasValue = false
const nextAttributes: UTSJSONObject = {}
if (attributes != null) {
if (getBooleanAttribute(attributes, 'bold')) {
nextAttributes['bold'] = true
hasValue = true
}
if (getBooleanAttribute(attributes, 'italic')) {
nextAttributes['italic'] = true
hasValue = true
}
if (getBooleanAttribute(attributes, 'underline')) {
nextAttributes['underline'] = true
hasValue = true
}
if (getBooleanAttribute(attributes, 'strike')) {
nextAttributes['strike'] = true
hasValue = true
}
if (getBooleanAttribute(attributes, 'blockquote')) {
nextAttributes['blockquote'] = true
hasValue = true
}
if (getBooleanAttribute(attributes, 'code-block')) {
nextAttributes['code-block'] = true
hasValue = true
}
if (attributes['header'] != null && (attributes['header'] as number) != 0) {
nextAttributes['header'] = attributes['header']
hasValue = true
}
if (attributes['list'] != null && (attributes['list'] as string) != '') {
nextAttributes['list'] = attributes['list']
hasValue = true
}
if (attributes['align'] != null && (attributes['align'] as string) != '') {
nextAttributes['align'] = attributes['align']
hasValue = true
}
if (attributes['textIndent'] != null && (attributes['textIndent'] as string) != '') {
nextAttributes['textIndent'] = attributes['textIndent']
hasValue = true
}
if (attributes['marginLeft'] != null && (attributes['marginLeft'] as string) != '') {
nextAttributes['marginLeft'] = attributes['marginLeft']
hasValue = true
}
if (attributes['marginRight'] != null && (attributes['marginRight'] as string) != '') {
nextAttributes['marginRight'] = attributes['marginRight']
hasValue = true
}
if (attributes['lineHeight'] != null && (attributes['lineHeight'] as string) != '') {
nextAttributes['lineHeight'] = attributes['lineHeight']
hasValue = true
}
if (attributes['letterSpacing'] != null && (attributes['letterSpacing'] as string) != '') {
nextAttributes['letterSpacing'] = attributes['letterSpacing']
hasValue = true
}
if (attributes['fontFamily'] != null && (attributes['fontFamily'] as string) != '') {
nextAttributes['fontFamily'] = attributes['fontFamily']
hasValue = true
}
if (attributes['fontSize'] != null && (attributes['fontSize'] as string) != '') {
nextAttributes['fontSize'] = attributes['fontSize']
hasValue = true
}
if (attributes['color'] != null && (attributes['color'] as string) != '') {
nextAttributes['color'] = attributes['color']
hasValue = true
}
if (attributes['backgroundColor'] != null && (attributes['backgroundColor'] as string) != '') {
nextAttributes['backgroundColor'] = attributes['backgroundColor']
hasValue = true
}
if (preserveLink && attributes['link'] != null && (attributes['link'] as string) != '') {
nextAttributes['link'] = attributes['link']
hasValue = true
}
}
if (nextLinkValue != null && nextLinkValue != '') {
nextAttributes['link'] = nextLinkValue
hasValue = true
}
return hasValue ? nextAttributes : null
}
function pushTextOp(ops: any[], text: string, attributes: any | null) {
if (text == '') {
return
}
if (attributes != null) {
ops.push({
insert: text,
attributes: attributes
})
return
}
ops.push({
insert: text
})
}
function replaceLinkInDeltaOps(ops: any[], selectionText: string, originalHref: string, nextHref: string | null) : any[] | null {
const nextOps: any[] = []
let replaced = false
for (const op of ops) {
const opItem = op as UTSJSONObject
if (replaced) {
nextOps.push(op)
continue
}
const insertValue = opItem['insert']
if (typeof insertValue != 'string') {
nextOps.push(op)
continue
}
const text = insertValue as string
const attributes = opItem['attributes'] as UTSJSONObject | null
const currentHref = attributes != null && attributes['link'] != null ? attributes['link'] as string : ''
const matchesExistingLink = originalHref != '' && currentHref == originalHref
const matchesPlainSelection = originalHref == '' && selectionText != '' && currentHref == ''
if (!matchesExistingLink && !matchesPlainSelection) {
nextOps.push(op)
continue
}
let matchStart = 0
let matchLength = text.length
if (selectionText != '') {
const index = text.indexOf(selectionText)
if (index < 0) {
nextOps.push(op)
continue
}
matchStart = index
matchLength = selectionText.length
}
const beforeText = text.slice(0, matchStart)
const matchedText = text.slice(matchStart, matchStart + matchLength)
const afterText = text.slice(matchStart + matchLength)
pushTextOp(nextOps, beforeText, buildOpAttributes(attributes, null, true))
const targetAttributes = buildOpAttributes(attributes, nextHref, false)
pushTextOp(nextOps, matchedText, targetAttributes)
pushTextOp(nextOps, afterText, buildOpAttributes(attributes, null, true))
replaced = true
}
if (!replaced) {
return null
}
return nextOps
}
function applyLinkDeltaMutation(nextHref: string | null) {
const editor = getEditor()
if (editor == null) {
return
}
const selectionText = data.linkSelectionText
const originalHref = data.linkOriginalHref
editor.getContents({
success: (res: UniEditorElementGetContentsOptionsRes) => {
const delta = res.delta as UTSJSONObject | null
const ops = delta != null ? delta['ops'] as any[] : null
if (ops == null) {
uni.showToast({
title: nextHref == null ? '删除链接失败' : '编辑链接失败',
icon: 'none'
})
return
}
const nextOps = replaceLinkInDeltaOps(ops, selectionText, originalHref, nextHref)
if (nextOps == null) {
uni.showToast({
title: nextHref == null ? '未定位到原超链接' : '未定位到原超链接文本',
icon: 'none'
})
return
}
editor.setContents({
delta: {
ops: nextOps
},
success: () => {
setLocalFormat('link', nextHref)
closeSheets()
closeLinkModal()
refocusEditorAfterToolbarAction()
},
fail: () => {
uni.showToast({
title: nextHref == null ? '删除链接失败' : '编辑链接失败',
icon: 'none'
})
}
})
},
fail: () => {
uni.showToast({
title: nextHref == null ? '读取内容失败' : '读取内容失败',
icon: 'none'
})
}
})
}
function insertLink(text: string, href: string) {
const editor = getEditor()
if (editor == null) {
return
}
const normalizedHref = normalizeLinkHref(href)
if (normalizedHref == '') {
uni.showToast({
title: '请输入链接地址',
icon: 'none'
})
return
}
const trimmedText = text.trim()
const linkText = trimmedText != '' ? trimmedText : normalizedHref
const options: UniEditorElementInsertLinkOptions = {
href: normalizedHref,
text: linkText,
success: () => {
data.formats.link = normalizedHref
},
fail: () => {
uni.showToast({
title: '插入链接失败',
icon: 'none'
})
}
}
editor.insertLink(options)
closeSheets()
closeLinkModal()
refocusEditorAfterToolbarAction()
}
function confirmLinkModal() {
const href = data.linkDraftHref.trim()
if (href == '') {
uni.showToast({
title: '链接地址不能为空',
icon: 'none'
})
return
}
// #ifndef WEB
const normalizedHref = normalizeLinkHref(href)
if (data.linkOriginalHref != '' || data.linkSelectionText != '') {
applyLinkDeltaMutation(normalizedHref)
return
}
// #endif
insertLink(data.linkDraftText, href)
}
function removeLink() {
const editor = getEditor()
if (editor == null) {
return
}
// #ifndef WEB
if (data.linkOriginalHref != '' || data.linkSelectionText != '') {
applyLinkDeltaMutation(null)
return
}
// #endif
applyFormatWithState('link', null)
closeSheets()
closeLinkModal()
refocusEditorAfterToolbarAction()
}
function chooseInsertImage() {
uni.chooseImage({
count: 1,
success: (res: ChooseImageSuccess) => {
const paths = res.tempFilePaths
if (paths.length > 0) {
insertImage(paths[0])
}
}
})
}
function undo() {
const editor = getEditor()
if (editor == null) {
return
}
editor.undo({
success: () => {
data.undoTest = true
},
fail: () => {
data.undoTest = false
}
})
closeSheets()
refocusEditorAfterToolbarAction()
}
function redo() {
const editor = getEditor()
if (editor == null) {
return
}
editor.redo({
success: () => {
data.redoTest = true
},
fail: () => {
data.redoTest = false
}
})
closeSheets()
refocusEditorAfterToolbarAction()
}
function showToolbarTooltip(buttonId: string, text: string) {
// #ifndef WEB
return
// #endif
const buttonElement = uni.getElementById(buttonId)
if (buttonElement == null) {
return
}
const rect = buttonElement!.getBoundingClientRect()
data.tooltipText = text
data.tooltipVisible = true
data.tooltipLeft = rect.left + rect.width / 2
data.tooltipTop = rect.bottom + 8
}
function hideToolbarTooltip() {
// #ifndef WEB
return
// #endif
if (!data.tooltipVisible && data.tooltipText == '') {
return
}
data.tooltipVisible = false
data.tooltipText = ''
}
// #ifdef WEB
function onToolbarMouseDown(event: UniMouseEvent) {
clearToolbarHideTimer()
hideToolbarTooltip()
data.ignoreNextBlur = true
data.isFocused = true
setTimeout(() => {
data.ignoreNextBlur = false
}, 0)
event.preventDefault()
}
// #endif
function onEditorFocus(event: UniEditorFocusEvent) {
console.log('onEditorFocus')
clearToolbarHideTimer()
data.editorInputType = ''
// #ifdef WEB
hideToolbarTooltip()
// #endif
if (data.activeSheet != '') {
data.activeSheet = ''
syncToolbarChipClasses()
}
data.isFocused = true
syncToolbarVisibility()
syncToolbarBottom()
if (data.formatPainterActive) {
setTimeout(() => {
if (!data.formatPainterActive) {
return
}
applyCopiedFormats()
clearFormatPainterState()
}, 0)
}
}
function onEditorBlur(event: UniEditorBlurEvent) {
if (data.ignoreNextBlur) {
data.isFocused = true
return
}
data.editorInputType = ''
data.isFocused = false
if (data.activeSheet == '') {
syncToolbarBottom()
syncToolbarVisibility()
scheduleToolbarHide()
}
}
function onStatusChange(event: UniEditorStatusChangeEvent) {
// #ifndef WEB
if (Date.now() < statusSyncLockedUntil) {
return
}
// #endif
const formats = event.detail
const formatObject = JSON.parse<UTSJSONObject>(JSON.stringify(formats))
if (formatObject == null) {
return
}
const nextFormats = createDefaultFormats()
nextFormats.bold = formats.bold == true
nextFormats.italic = formats.italic == true
nextFormats.underline = formats.underline == true
nextFormats.strike = formats.strike == true
nextFormats.blockquote = formats.blockquote == true
const codeBlockValue = formatObject['code-block']
nextFormats.codeBlock = codeBlockValue == true
const linkValue = formatObject['link']
if (linkValue != null) {
nextFormats.link = linkValue as string
} else if (data.linkModalVisible) {
nextFormats.link = data.formats.link
}
if (formats.header != null) {
nextFormats.header = formats.header
}
if (formats.list != null) {
nextFormats.list = formats.list
}
if (formats.align != null) {
nextFormats.align = formats.align
}
const textIndentValue = formatObject['textIndent']
if (textIndentValue != null) {
nextFormats.textIndent = textIndentValue as string
}
const marginLeftValue = formatObject['marginLeft']
if (marginLeftValue != null) {
nextFormats.marginLeft = marginLeftValue as string
}
const marginRightValue = formatObject['marginRight']
if (marginRightValue != null) {
nextFormats.marginRight = marginRightValue as string
}
if (formats.lineHeight != null) {
nextFormats.lineHeight = formats.lineHeight
}
if (formats.letterSpacing != null) {
nextFormats.letterSpacing = formats.letterSpacing
}
if (formats.fontFamily != null) {
nextFormats.fontFamily = formats.fontFamily
}
if (formats.fontSize != null) {
nextFormats.fontSize = formats.fontSize
}
if (formats.color != null) {
nextFormats.color = formats.color
}
if (formats.backgroundColor != null) {
nextFormats.backgroundColor = formats.backgroundColor
}
assignFormats(data.formats, nextFormats)
}
onReady(() => {
updatePageWindowHeight()
})
onResize((options: OnResizeOptions) => {
data.pageWindowHeight = options.size.windowHeight
data.pageWindowWidth = options.size.windowWidth
if (data.activeSheet != '') {
updateSheetAnchor(data.activeSheet)
}
})
onUnload(() => {
clearToolbarHideTimer()
})
defineExpose({
data,
openMoreSheet,
openTitleSheet,
openStyleSheet,
openTextColorSheet,
openBackgroundColorSheet,
openLineHeightSheet,
openLetterSpacingSheet,
openBlockIndentSheet,
openListSheet,
openFontSizeSheet,
openFontFamilySheet,
openAlignSheet,
closeSheets,
getPanelTitle,
getPanelSubtitle,
getTitleSummary,
setToolbarFormatsForTest,
applyToolbarPresetForTest,
resetFormatsForTest,
setParagraph,
setHeadingLevel,
toggleBold,
toggleItalic,
toggleUnderline,
toggleStrike,
setTextColorAndClose,
setBackgroundColorAndClose,
setLineHeightAndClose,
setLetterSpacingAndClose,
setFontSizeAndClose,
setFontFamilyAndClose,
toggleTextIndent,
setBlockIndentAndClose,
setAlignLeft,
setAlignCenter,
setAlignRight,
setAlignJustify,
toggleBlockquote,
toggleCodeBlock,
setBulletList,
setOrderedList,
toggleChecklistTool,
openLinkModal,
closeLinkModal,
confirmLinkModal,
removeLink,
insertLink,
setContents,
blur,
clear,
insertDivider,
undo,
redo,
insertImage,
removeFormat,
getCon
})
</script>
<style>
@import './editor-icon.css';
.page-shell {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
background-color: #edf1f4;
padding-top: 16px;
padding-right: 12px;
padding-bottom: 12px;
padding-left: 12px;
}
.page-shell-wide {
padding-top: 24px;
padding-right: 24px;
padding-bottom: 20px;
padding-left: 24px;
}
.editor-layout {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
max-width: 1320px;
align-self: center;
}
.editor-layout-wide {
max-width: 1360px;
}
.editor-stage {
flex: 1;
display: flex;
flex-direction: column;
width: 100%;
/* max-width: 1120px; */
align-self: center;
background-color: #ffffff;
border-width: 1px;
border-color: #d6dde4;
border-radius: 0px;
/* box-shadow: 0px 18px 36px rgba(15, 23, 42, 0.07); */
overflow: hidden;
}
/* #ifdef WEB */
.editor-stage {
border-top-width: 0px;
border-top-left-radius: 0px;
border-top-right-radius: 0px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
}
/* #endif */
.editor-field {
flex: 1;
background-color: #ffffff;
}
.toolbar-wrap {
position: relative;
left: auto;
right: auto;
bottom: auto;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
z-index: 130;
margin-bottom: 0px;
box-shadow: none;
}
.toolbar-frame {
width: 100%;
align-self: center;
display: flex;
flex-direction: column;
background-color: #fafafa;
border-width: 1px;
border-color: #ebebeb;
border-bottom-width: 1px;
border-radius: 0px;
padding-top: 0px;
padding-right: 0px;
padding-bottom: 0px;
padding-left: 0px;
}
.sheet-host {
position: fixed;
left: 0px;
right: 0px;
bottom: 0px;
display: flex;
flex-direction: column;
z-index: 131;
}
.sheet-backdrop {
position: fixed;
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
z-index: 129;
background-color: rgba(15, 23, 42, 0.04);
}
.sheet-host-floating {
position: absolute;
left: 12px;
right: auto;
top: 58px;
bottom: auto;
z-index: 160;
box-shadow: 0px 10px 24px rgba(25, 25, 25, 0.05);
border-radius: 14px;
}
.sheet-host-mobile-web {
left: 0px;
right: 0px;
bottom: 0px;
}
.toolbar-panel {
overflow: hidden;
background-color: rgba(255, 255, 255, 0.98);
border-width: 1px;
border-color: #d6dde4;
padding-top: 14px;
padding-right: 14px;
padding-bottom: 12px;
padding-left: 14px;
/* box-shadow: 0px 10px 24px rgba(25, 25, 25, 0.12); */
}
.toolbar-panel-attached {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
border-top-width: 0px;
/* box-shadow: 0px 10px 20px rgba(25, 25, 25, 0.08); */
}
.toolbar-shell {
align-self: center;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
background-color: #fafafa;
border-width: 0px;
border-radius: 0px;
min-height: 40px;
padding-top: 0px;
padding-right: 0px;
padding-bottom: 0px;
padding-left: 0px;
box-shadow: none;
}
.toolbar-shell-compact {
flex-wrap: wrap;
align-items: center;
justify-content: center;
padding-top: 6px;
padding-bottom: 6px;
}
.toolbar-group {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
}
.toolbar-group-compact {
justify-content: center;
margin-top: 0px;
}
.toolbar-group-leading {
min-width: 70px;
}
.toolbar-group-leading-compact {
margin-top: 0px;
}
.toolbar-group-actions {
margin-left: auto;
}
.toolbar-group-actions .toolbar-icon-button {
margin-left: 2px;
margin-right: 0px;
}
.toolbar-divider {
width: 1px;
height: 20px;
background-color: #d8d8d8;
margin-left: 3px;
margin-right: 3px;
}
.toolbar-divider-hidden {
display: none;
}
.toolbar-select {
height: 28px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-width: 1px;
border-color: transparent;
border-radius: 4px;
background-color: transparent;
padding-left: 5px;
padding-right: 5px;
margin-right: 1px;
transition-property: background-color, border-color;
transition-duration: 160ms;
}
.toolbar-select-value {
padding-left: 4px;
padding-right: 4px;
}
.toolbar-select-active {
background-color: #e7eaee;
border-color: #d7dde5;
}
.toolbar-select-label {
color: #50565f;
font-size: 12px;
line-height: 18px;
}
.toolbar-select-glyph {
font-size: 16px;
line-height: 16px;
}
.toolbar-size-label {
font-size: 14px;
line-height: 18px;
/* padding: 0px 5px; */
}
.toolbar-select-font-label {
font-size: 12px;
line-height: 16px;
/* padding: 0px 5px; */
}
.toolbar-select-mini-label {
min-width: 10px;
margin-left: 3px;
font-size: 9px;
line-height: 12px;
text-align: center;
}
.toolbar-select-label-active {
color: #2b3137;
}
.toolbar-shell-dense .toolbar-select {
padding-left: 4px;
padding-right: 4px;
}
.toolbar-shell-dense .toolbar-select-value,
.toolbar-shell-dense .toolbar-select-compact {
min-width: 40px;
}
.toolbar-shell-dense .toolbar-icon-text {
font-size: 16px;
line-height: 16px;
}
.toolbar-shell-dense .toolbar-select-font-label,
.toolbar-shell-dense .toolbar-select-label {
font-size: 11px;
}
.toolbar-select-arrow {
min-width: 8px;
color: #6b7280;
font-size: 4px;
line-height: 12px;
text-align: center;
margin-left: 2px;
margin-top: 1px;
}
.toolbar-select-arrow.iconfont {
font-size: 4px;
line-height: 12px;
}
.toolbar-select-arrow-active {
color: #475569;
}
.toolbar-icon-button {
width: 28px;
height: 28px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-width: 1px;
border-color: transparent;
border-radius: 4px;
background-color: transparent;
margin-right: 1px;
transition-property: background-color, border-color;
transition-duration: 160ms;
}
.toolbar-icon-button-disabled {
opacity: 0.42;
}
.toolbar-color-button {
height: 28px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-width: 1px;
border-color: transparent;
border-radius: 4px;
background-color: transparent;
margin-left: 1px;
padding-left: 4px;
padding-right: 4px;
transition-property: background-color, border-color;
transition-duration: 160ms;
}
.toolbar-color-button-active {
border-color: #d7dde5;
background-color: #e7eaee;
}
.toolbar-color-label {
color: #334155;
font-size: 14px;
line-height: 18px;
font-weight: 700;
}
.toolbar-color-label-active {
color: #111827;
}
.toolbar-color-symbol {
color: #50565f;
font-size: 15px;
line-height: 15px;
font-weight: 600;
}
.toolbar-color-symbol-active {
color: #111827;
}
.toolbar-color-stack {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.toolbar-color-underline {
width: 12px;
height: 2px;
border-radius: 2px;
margin-top: -1px;
margin-left: 1px;
}
.toolbar-color-ab {
font-size: 13px;
}
.toolbar-color-arrow {
min-width: 8px;
color: #6b7280;
font-size: 4px;
line-height: 12px;
text-align: center;
margin-left: 0px;
margin-top: 1px;
}
.toolbar-color-arrow.iconfont {
font-size: 4px;
line-height: 12px;
}
.toolbar-color-arrow-active {
color: #475569;
}
.toolbar-icon-button-active {
background-color: #e7eaee;
border-color: #d7dde5;
}
.toolbar-icon-text {
color: #50565f;
font-size: 17px;
line-height: 17px;
}
.toolbar-symbol-text {
font-size: 14px;
line-height: 16px;
}
.toolbar-symbol-text-small {
font-size: 11px;
}
.link-modal-mask {
position: fixed;
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
z-index: 220;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(15, 23, 42, 0.26);
padding-left: 16px;
padding-right: 16px;
}
.link-modal-mask-floating {
background-color: rgba(15, 23, 42, 0.26);
}
.link-modal-mask-app {
background-color: rgba(15, 23, 42, 0.38);
}
.link-modal-card {
display: flex;
flex-direction: column;
background-color: #ffffff;
border-radius: 14px;
padding-top: 20px;
padding-right: 18px;
padding-bottom: 18px;
padding-left: 18px;
}
.link-modal-card-floating {
width: 100%;
max-width: 420px;
box-shadow: 0px 18px 36px rgba(15, 23, 42, 0.16);
}
.link-modal-card-app {
width: 84%;
border-width: 1px;
border-color: #dbe4ee;
background-color: #ffffff;
}
.link-modal-title-app {
color: #111827;
font-size: 20px;
line-height: 28px;
font-weight: 700;
}
.link-modal-subtitle-app {
color: #64748b;
font-size: 14px;
line-height: 20px;
}
.link-modal-label-app {
color: #0f172a;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.link-modal-header {
display: flex;
flex-direction: column;
}
.link-modal-title {
color: #111827;
font-size: 17px;
line-height: 24px;
font-weight: 600;
}
.link-modal-subtitle {
margin-top: 4px;
color: #6b7280;
font-size: 13px;
line-height: 18px;
}
.link-modal-form {
display: flex;
flex-direction: column;
margin-top: 16px;
}
.link-modal-field {
display: flex;
flex-direction: column;
margin-top: 12px;
}
.link-modal-label {
color: #374151;
font-size: 13px;
line-height: 18px;
}
.link-modal-input {
height: 40px;
margin-top: 8px;
border-width: 1px;
border-color: #d1d5db;
border-radius: 10px;
padding-left: 12px;
padding-right: 12px;
background-color: #f8fafc;
color: #111827;
font-size: 14px;
}
.link-modal-input-app {
border-color: #cbd5e1;
background-color: #ffffff;
color: #111827;
}
.link-modal-selection-chip {
min-height: 40px;
margin-top: 8px;
display: flex;
flex-direction: column;
justify-content: center;
border-radius: 10px;
padding-left: 12px;
padding-right: 12px;
background-color: #eef2ff;
}
.link-modal-selection-text {
color: #1e3a8a;
font-size: 14px;
line-height: 20px;
}
.link-modal-selection-chip-app {
border-width: 1px;
border-color: #c7d2fe;
background-color: #eef2ff;
}
.link-modal-selection-text-app {
color: #1d4ed8;
}
.link-modal-actions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 20px;
}
.link-modal-actions-app {
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
}
.link-modal-danger {
min-height: 36px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-left: 10px;
padding-right: 10px;
}
.link-modal-danger-app {
width: 100%;
min-width: 0px;
margin-bottom: 12px;
border-width: 1px;
border-color: #fecaca;
border-radius: 10px;
background-color: #fff5f5;
}
.link-modal-danger-text {
color: #dc2626;
font-size: 13px;
line-height: 18px;
font-weight: 600;
}
.link-modal-danger-text-app {
color: #dc2626;
}
.link-modal-action-group {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
}
.link-modal-action-group-app {
flex: 1;
width: 100%;
justify-content: flex-end;
}
.link-modal-button {
min-width: 74px;
min-height: 36px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 10px;
padding-left: 14px;
padding-right: 14px;
}
.link-modal-button-secondary {
background-color: #eef2f7;
}
.link-modal-button-primary {
margin-left: 10px;
background-color: #2563eb;
}
.link-modal-button-secondary-app {
flex: 1;
border-width: 1px;
border-color: #d6dde8;
background-color: #f8fafc;
}
.link-modal-button-primary-app {
flex: 1;
background-color: #1d4ed8;
}
.link-modal-button-text {
color: #ffffff;
font-size: 14px;
line-height: 20px;
font-weight: 600;
}
.link-modal-button-text-app {
color: #ffffff;
}
.link-modal-button-text-secondary {
color: #334155;
}
.link-modal-button-text-secondary-app {
color: #334155;
}
.toolbar-font-action-italic {
font-style: italic;
}
.toolbar-font-action-underline {
text-decoration-line: underline;
}
.toolbar-font-action-strike {
text-decoration-line: line-through;
}
.toolbar-icon-text-active {
color: #2b3137;
}
.relative-top-1 {
position: relative;
top: 1px;
}
.desktop-toolbar-mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.5);
}
/* #ifdef WEB */
.toolbar-tooltip {
position: fixed;
z-index: 131;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
transform: translateX(-50%);
overflow: visible;
}
.toolbar-tooltip-bubble {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(15, 23, 42, 0.92);
border-radius: 4px;
padding-top: 5px;
padding-right: 8px;
padding-bottom: 5px;
padding-left: 8px;
box-shadow: 0px 8px 20px rgba(15, 23, 42, 0.18);
}
.toolbar-tooltip-text {
color: #ffffff;
font-size: 11px;
line-height: 14px;
}
.toolbar-tooltip-arrow {
width: 8px;
height: 8px;
margin-bottom: -4px;
background-color: rgba(15, 23, 42, 0.92);
transform: rotate(45deg);
border-radius: 1px;
}
.toolbar-select:hover,
.toolbar-icon-button:hover,
.toolbar-color-button:hover {
background-color: #f3f4f6;
border-color: #e4e7eb;
}
/* #endif */
.toolbar-panel-floating {
position: relative;
height: auto;
border-width: 1px;
border-color: #cfd6dd;
border-radius: 8px;
padding-top: 8px;
padding-right: 8px;
padding-bottom: 8px;
padding-left: 8px;
background-color: #ffffff;
box-shadow: 0px 10px 24px rgba(15, 23, 42, 0.08);
transition-property: transform, opacity;
transition-duration: 180ms;
opacity: 0;
transform: translateY(-8px);
}
.toolbar-panel-section .desktop-color-sheet-label {
display: none;
}
.toolbar-panel-floating-attached {
border-top-width: 1px;
}
.toolbar-panel-floating-entered {
opacity: 1;
transform: translateY(0px);
}
.toolbar-panel-mobile-web {
border-top-left-radius: 18px;
border-top-right-radius: 18px;
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
border-top-width: 1px;
padding-top: 16px;
padding-right: 12px;
padding-bottom: 16px;
padding-left: 12px;
box-shadow: 0px -10px 30px rgba(15, 23, 42, 0.12);
}
.toolbar-panel-arrow {
position: absolute;
top: -7px;
width: 14px;
height: 14px;
background-color: #ffffff;
border-width: 0px;
box-shadow: none;
transform: rotate(45deg);
}
.toolbar-panel-header-floating {
margin-bottom: 6px;
}
.toolbar-panel-header-mobile-web {
margin-bottom: 10px;
}
.toolbar-panel-title-floating {
font-size: 12px;
}
.toolbar-panel-title-mobile-web {
font-size: 15px;
}
.toolbar-panel-subtitle-floating {
font-size: 10px;
margin-top: 2px;
}
.toolbar-panel-subtitle-mobile-web {
font-size: 11px;
margin-top: 4px;
}
.toolbar-panel-scroll-floating {
flex: 0;
}
.toolbar-panel-scroll-mobile-web {
flex: 0;
/* max-height: 62vh; */
}
.toolbar-chip-row-panel {
margin-left: 0px;
margin-right: 0px;
}
.toolbar-chip-row-panel-compact {
margin-left: 0px;
margin-right: 0px;
}
.toolbar-panel-section .desktop-color-sheet-label {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 12px;
}
.desktop-color-sheet-icon {
width: 14px;
height: 14px;
border-width: 1px;
border-color: #6b7280;
border-radius: 2px;
margin-right: 10px;
transform: rotate(45deg);
}
.desktop-color-sheet-label-text {
color: #374151;
font-size: 12px;
line-height: 18px;
}
.desktop-menu-item {
width: 100%;
min-width: 0px;
min-height: 0px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
border-width: 1px;
border-color: transparent;
border-radius: 6px;
background-color: #ffffff;
padding-top: 8px;
padding-right: 10px;
padding-bottom: 8px;
padding-left: 10px;
margin-left: 0px;
margin-right: 0px;
margin-bottom: 4px;
}
.desktop-menu-item-active {
background-color: #eef3f8;
border-color: #ccd6e0;
}
.desktop-menu-badge {
min-width: 24px;
height: 24px;
border-radius: 4px;
background-color: #f1f5f9;
color: #475569;
font-size: 11px;
font-weight: 700;
text-align: center;
line-height: 24px;
margin-right: 10px;
}
.desktop-menu-badge-active {
background-color: #dbe7f3;
color: #0f172a;
}
.desktop-menu-text {
color: #1f2937;
font-size: 13px;
text-align: left;
line-height: 18px;
letter-spacing: 0px;
}
.desktop-menu-text-active {
color: #0f172a;
}
.toolbar-panel-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.toolbar-panel-title-wrap {
display: flex;
flex-direction: column;
flex: 1;
}
.toolbar-panel-title {
color: #0f172a;
font-size: 14px;
font-weight: 600;
letter-spacing: 0.4px;
}
.toolbar-panel-subtitle {
color: #64748b;
font-size: 11px;
margin-top: 4px;
line-height: 16px;
}
.toolbar-panel-close {
width: 28px;
height: 28px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 14px;
background-color: #f3f1ec;
}
.toolbar-panel-close-icon {
width: 14px;
height: 14px;
}
.toolbar-panel-section {
display: flex;
flex-direction: column;
}
.toolbar-panel-scroll {
flex: 1;
display: flex;
flex-direction: column;
}
.toolbar-panel-group {
display: flex;
flex-direction: column;
margin-bottom: 6px;
}
.toolbar-chip-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
margin-left: -4px;
margin-right: -4px;
}
.toolbar-chip-cell {
width: 25%;
padding-left: 4px;
padding-right: 4px;
margin-bottom: 8px;
}
.toolbar-chip {
min-width: 72px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 14px;
background-color: #f3f6f9;
border-width: 1px;
border-color: #d6dde4;
padding-top: 10px;
padding-right: 12px;
padding-bottom: 10px;
padding-left: 12px;
margin-left: 4px;
margin-right: 4px;
margin-bottom: 8px;
}
.toolbar-chip-grid {
width: 100%;
min-width: 0px;
min-height: 72px;
justify-content: flex-start;
margin-left: 0px;
margin-right: 0px;
margin-bottom: 0px;
padding-top: 12px;
padding-right: 8px;
padding-bottom: 10px;
padding-left: 8px;
}
.toolbar-color-section {
display: flex;
flex-direction: column;
margin-top: 4px;
}
.toolbar-color-title {
color: #64748b;
font-size: 12px;
margin-bottom: 8px;
letter-spacing: 0.3px;
}
.toolbar-chip-cell {
padding-left: 3px;
padding-right: 3px;
margin-bottom: 6px;
}
.toolbar-chip-cell-quarter {
width: 25%;
}
.toolbar-chip-cell-full {
width: 100%;
}
.toolbar-chip-cell-third {
width: 33.33%;
}
.toolbar-chip-cell-half {
width: 50%;
padding-left: 2px;
padding-right: 2px;
}
.color-row-panel {
width: 340px;
align-self: center;
margin-left: 0px;
margin-right: 0px;
}
.toolbar-color-title {
font-size: 11px;
margin-bottom: 6px;
}
.color-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
margin-left: -4px;
margin-right: -4px;
}
.color-swatch {
width: 48px;
height: 36px;
border-radius: 10px;
border-width: 2px;
border-style: solid;
border-color: rgba(15, 23, 42, 0.08);
margin-left: 4px;
margin-right: 4px;
margin-bottom: 10px;
}
.color-swatch.color-swatch-panel {
width: 28px;
height: 28px;
border-radius: 4px;
border-width: 1px;
margin-left: 3px;
margin-right: 3px;
margin-bottom: 10px;
}
.color-swatch-active {
border-color: #111827;
border-width: 3px;
/* box-shadow: 0px 0px 0px 2px rgba(31, 31, 31, 0.12); */
}
.color-swatch-clear {
background-color: #ffffff;
border-color: #cbd5e1;
}
</style>