feat: codeblock 增加换行设置按钮

This commit is contained in:
yanmao 2021-12-18 17:01:07 +08:00
parent 8bbb9d0dc4
commit 27798432aa
30 changed files with 1156 additions and 827 deletions

View File

@ -370,6 +370,11 @@ abstract class CardEntry<T extends CardValue = {}> implements CardInterface {
this.resizeModel?.render(container);
}
}
this.toolbarModel?.hide();
this.toolbarModel?.destroy();
if (this.toolbar) {
this.toolbarModel = new Toolbar(this.editor, this);
}
if (this.isEditable) {
this.editor.nodeId.generateAll(this.getCenter().get<Element>()!);
}

View File

@ -20,7 +20,10 @@ import './index.css';
export const isCardToolbarItemOptions = (
item: ToolbarItemOptions | CardToolbarItemOptions,
): item is CardToolbarItemOptions => {
return ['button', 'input', 'dropdown', 'node'].indexOf(item.type) === -1;
return (
['button', 'input', 'dropdown', 'node', 'switch'].indexOf(item.type) ===
-1
);
};
class CardToolbar implements CardToolbarInterface {

View File

@ -10,9 +10,11 @@ const template = (options: DropdownSwitchOptions) => {
<span class="data-toolbar-dropdown-item-content"${
options.disabled ? ' disabled="disabled"' : ''
}>${options.content}</span>
<button type="button" role="switch" aria-checked="true" class="switch-btn ${
checked ? ' switch-checked' : ''
}">
<button type="button"${
options.disabled ? ' disabled="disabled"' : ''
} role="switch" aria-checked="true" class="switch-btn ${
checked ? ' switch-checked' : ''
}">
<div class="switch-handle"></div>
<span class="switch-inner"></span>
</button>
@ -30,7 +32,7 @@ export default class {
renderTo(container: NodeInterface) {
this.root = $(template(this.options));
this.switch = this.root.find('.ant-switch');
this.switch = this.root.find('.switch-btn');
container.append(this.root);
this.root.on('mousedown', (e) => e.preventDefault());
const { onClick } = this.options;
@ -46,9 +48,9 @@ export default class {
updateSwitch() {
if (this.options.getState) {
if (this.options.getState()) {
this.switch?.addClass('ant-switch-checked');
this.switch?.addClass('switch-checked');
} else {
this.switch?.removeClass('ant-switch-checked');
this.switch?.removeClass('switch-checked');
}
}
}

View File

@ -155,6 +155,7 @@
.data-toolbar-dropdown-switch .data-toolbar-dropdown-item-content {
flex: 1;
margin-right: 4px;
}
.data-toolbar-dropdown-switch .switch-btn {
@ -231,4 +232,96 @@
.data-toolbar-dropdown-switch .switch-btn.switch-checked .switch-inner {
margin: 0 18px 0 5px;
}
.data-toolbar-switch {
display: flex;
align-items: center;
padding: 0 4px;
cursor: pointer;
}
.data-toolbar-switch:hover {
background-color: #f4f4f4;
border-radius: 2px;
}
.data-toolbar-switch .switch-content {
flex: 1;
margin-right: 4px;
}
.data-toolbar-switch .switch-btn {
margin: 0;
padding: 0;
color: #595959;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
list-style: none;
-webkit-font-feature-settings: "tnum";
font-feature-settings: "tnum";
position: relative;
display: inline-block;
-webkit-box-sizing: border-box;
box-sizing: border-box;
vertical-align: middle;
background-color: rgba(0,0,0,.25);
border: 0;
border-radius: 100px;
cursor: pointer;
-webkit-transition: all .2s;
transition: all .2s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
min-width: 28px;
height: 16px;
line-height: 16px;
}
.data-toolbar-switch .switch-btn.switch-checked {
background-color: #347EFF
}
.data-toolbar-switch .switch-btn .switch-handle {
top: 2px;
left: 2px;
width: 12px;
height: 12px;
}
.data-toolbar-switch .switch-btn .switch-handle, .data-toolbar-switch .switch-btn .switch-handle:before {
position: absolute;
-webkit-transition: all .2s ease-in-out;
transition: all .2s ease-in-out;
}
.data-toolbar-switch .switch-btn.switch-checked .switch-handle {
left: calc(100% - 14px);
}
.data-toolbar-switch .switch-btn .switch-handle:before {
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #fff;
border-radius: 9px;
-webkit-box-shadow: 0 2px 4px 0 rgb(0 35 11 / 20%);
box-shadow: 0 2px 4px 0 rgb(0 35 11 / 20%);
content: "";
}
.data-toolbar-switch .switch-btn .switch-inner {
display: block;
margin: 0 5px 0 18px;
font-size: 12px;
color: #fff;
-webkit-transition: margin .2s;
transition: margin .2s;
}
.data-toolbar-switch .switch-btn.switch-checked .switch-inner {
margin: 0 18px 0 5px;
}

View File

@ -6,11 +6,13 @@ import {
NodeOptions,
ToolbarOptions,
ToolbarInterface,
SwitchOptions,
} from '../types/toolbar';
import Button from './button';
import Dropdown from './dropdown';
import Input from './input';
import Tooltip from './tooltip';
import Switch from './switch';
import { DATA_ELEMENT } from '../constants';
import { $ } from '../node';
import './index.css';
@ -22,7 +24,8 @@ const template = () => {
class Toolbar implements ToolbarInterface {
private options: ToolbarOptions;
root: NodeInterface;
private items: Array<NodeInterface | Button | Input | Dropdown> = [];
private items: Array<NodeInterface | Button | Input | Dropdown | Switch> =
[];
constructor(options: ToolbarOptions) {
this.options = { ...options };
@ -36,6 +39,10 @@ class Toolbar implements ToolbarInterface {
item = new Button(options as ButtonOptions);
item.render(node);
}
if (options.type === 'switch') {
item = new Switch(options as SwitchOptions);
item.render(node);
}
if (options.type === 'input') {
const inputOptions = options as InputOptions;
item = new Input(inputOptions);
@ -102,4 +109,4 @@ class Toolbar implements ToolbarInterface {
export default Toolbar;
export { Button, Input, Dropdown, Tooltip };
export { Button, Input, Dropdown, Tooltip, Switch };

View File

@ -0,0 +1,63 @@
import { NodeInterface } from '../types/node';
import { SwitchOptions } from '../types/toolbar';
import { $ } from '../node';
const template = (options: SwitchOptions) => {
let checked = !!options.checked;
if (options.getState) checked = options.getState();
return `
<div class="data-toolbar-switch">
<span class="switch-content"${
options.disabled ? ' disabled="disabled"' : ''
}>${options.content}</span>
<button type="button" role="switch" aria-checked="true" class="switch-btn ${
checked ? ' switch-checked' : ''
}"${options.disabled ? ' disabled="disabled"' : ''}>
<div class="switch-handle"></div>
<span class="switch-inner"></span>
</button>
</div>`;
};
export default class {
private options: SwitchOptions;
private root: NodeInterface;
private switch: NodeInterface | undefined;
constructor(options: SwitchOptions) {
this.options = options;
this.root = $(template(options));
this.switch = this.root.find('.switch-btn');
if (options.class) {
this.root.addClass(options.class);
}
}
render(container: NodeInterface) {
const { didMount, onClick } = this.options;
container.append(this.root);
this.root.on('mousedown', (e) => e.preventDefault());
this.root.on('click', (e) => {
e.stopPropagation();
if (onClick) {
onClick(e, this.root);
this.updateSwitch();
}
});
if (didMount) {
didMount(this.root);
}
}
updateSwitch() {
if (this.options.getState) {
if (this.options.getState()) {
this.switch?.addClass('switch-checked');
} else {
this.switch?.removeClass('switch-checked');
}
}
}
}

View File

@ -41,7 +41,40 @@ export type ButtonOptions = {
*/
didMount?: (node: NodeInterface) => void;
};
export type SwitchOptions = {
/**
*
*/
type: 'switch';
/**
*
*/
class?: string;
/**
*
*/
disabled?: boolean;
/**
*
*/
content: string;
/**
*
*/
checked?: boolean;
/**
*
*/
getState?: () => boolean;
/**
*
*/
onClick?: (event: MouseEvent, node: NodeInterface) => void;
/**
*
*/
didMount?: (node: NodeInterface) => void;
};
/**
*
*/
@ -190,7 +223,8 @@ export type ToolbarItemOptions =
| ButtonOptions
| InputOptions
| DropdownOptions
| NodeOptions;
| NodeOptions
| SwitchOptions;
/**
*

View File

@ -58,6 +58,9 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
return {
tabSize,
indentUnit: tabSize,
scrollbarStyle: 'simple',
readOnly: !isEngine(this.editor) || this.editor.readonly,
viewportMargin: Infinity,
};
}
@ -77,9 +80,6 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
lineWrapping: false,
autofocus: false,
dragDrop: false,
readOnly: !isEngine(this.editor) || this.editor.readonly,
scrollbarStyle: 'null',
viewportMargin: 1 / 0,
...this.getConfig(value, syntaxMode),
...options,
},
@ -141,6 +141,10 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
return this.codeMirror;
}
setAutoWrap(value: boolean) {
this.codeMirror?.setOption('lineWrapping', value);
}
update(mode: string, code?: string) {
this.mode = mode;
if (code !== undefined) {
@ -154,7 +158,7 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
if (code !== undefined) this.save();
}
render(mode: string, value: string) {
render(mode: string, value: string, options?: EditorConfiguration) {
const root = this.container.find('.data-codeblock-content');
mode = this.getSyntax(mode);
const stage = $(
@ -162,7 +166,10 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
);
root.append(stage);
const pre = stage.find('pre')[0];
this.runMode(value || '', mode, pre, this.getConfig(value, mode));
this.runMode(value || '', mode, pre, {
...this.getConfig(value, mode),
...options,
});
}
save() {

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@ import './index.css';
export type CodeBlockValue = {
mode?: string;
code?: string;
autoWrap?: boolean;
};
class CodeBlcok extends Card<CodeBlockValue> {
@ -85,10 +86,30 @@ class CodeBlcok extends Card<CodeBlockValue> {
},
});
}
#viewAutoWrap?: boolean = undefined;
toolbar(): Array<CardToolbarItemOptions | ToolbarItemOptions> {
if (!isEngine(this.editor) || this.editor.readonly) {
return [{ type: 'copy' }];
return [
{ type: 'copy' },
{
type: 'switch',
content: this.editor.language.get<string>(
CodeBlcok.cardName,
'autoWrap',
),
getState: () => {
if (this.#viewAutoWrap === undefined) {
this.#viewAutoWrap = !!this.getValue()?.autoWrap;
}
return this.#viewAutoWrap;
},
onClick: () => {
const autoWrap = !this.#viewAutoWrap;
this.#viewAutoWrap = autoWrap;
this.codeEditor?.setAutoWrap(autoWrap);
},
},
];
}
return [
{
@ -120,6 +141,24 @@ class CodeBlcok extends Card<CodeBlockValue> {
}, 100);
},
},
{
type: 'switch',
content: this.editor.language.get<string>(
CodeBlcok.cardName,
'autoWrap',
),
getState: () => {
return !!this.getValue()?.autoWrap;
},
onClick: () => {
const value = this.getValue();
const autoWrap = !value?.autoWrap;
this.setValue({
autoWrap,
});
this.codeEditor?.setAutoWrap(autoWrap);
},
},
];
}
@ -142,15 +181,18 @@ class CodeBlcok extends Card<CodeBlockValue> {
if (isEngine(this.editor)) {
if (this.mirror) {
this.codeEditor.update(mode, code);
this.codeEditor.setAutoWrap(!!value?.autoWrap);
return;
}
setTimeout(() => {
this.mirror = this.codeEditor?.create(mode, code);
// 创建后更新一下toolbar不然无法选择语言
if (this.activated) this.toolbarModel?.show();
this.mirror = this.codeEditor?.create(mode, code, {
lineWrapping: !!value?.autoWrap,
});
}, 50);
} else {
this.codeEditor.render(mode, code);
this.codeEditor?.create(mode, code, {
lineWrapping: !!value?.autoWrap,
});
}
}
}

View File

@ -30,3 +30,5 @@ import 'codemirror/mode/vb/vb';
import 'codemirror/mode/velocity/velocity';
import 'codemirror/mode/xml/xml';
import 'codemirror/mode/yaml/yaml';
import 'codemirror/addon/scroll/simplescrollbars';
import 'codemirror/addon/scroll/simplescrollbars.css';

View File

@ -23,7 +23,8 @@ export interface CodeBlockEditorInterface {
getSyntax(mode: string): string;
create(mode: string, value: string, options?: EditorConfiguration): Editor;
update(mode: string, value?: string): void;
render(mode: string, value: string): void;
setAutoWrap(value: boolean): void;
render(mode: string, value: string, options?: EditorConfiguration): void;
save(): void;
focus(): void;
/**

View File

@ -16,6 +16,7 @@ import {
decodeCardValue,
} from '@aomao/engine';
import CodeBlockComponent, { CodeBlockEditor } from './component';
import locales from './locales';
export interface Options extends PluginOptions {
hotkey?: string | Array<string>;
@ -42,6 +43,7 @@ export default class extends Plugin<Options> {
}
init() {
this.editor.language.add(locales);
this.editor.on('parse:html', (node) => this.parseHtml(node));
this.editor.on('paste:schema', (schema) => this.pasteSchema(schema));
this.editor.on('paste:each', (child) => this.pasteHtml(child));

View File

@ -0,0 +1,5 @@
export default {
codeblock: {
autoWrap: 'Auto Wrap',
},
};

View File

@ -0,0 +1,7 @@
import en from './en-US';
import cn from './zh-CN';
export default {
'en-US': en,
'zh-CN': cn,
};

View File

@ -0,0 +1,5 @@
export default {
codeblock: {
autoWrap: '自动换行',
},
};

View File

@ -58,6 +58,9 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
return {
tabSize,
indentUnit: tabSize,
scrollbarStyle: 'simple',
readOnly: !isEngine(this.editor) || this.editor.readonly,
viewportMargin: Infinity,
};
}
@ -77,9 +80,6 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
lineWrapping: false,
autofocus: false,
dragDrop: false,
readOnly: !isEngine(this.editor) || this.editor.readonly,
scrollbarStyle: 'null',
viewportMargin: 1 / 0,
...this.getConfig(value, syntaxMode),
...options,
},
@ -142,6 +142,10 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
return this.codeMirror;
}
setAutoWrap(value: boolean) {
this.codeMirror?.setOption('lineWrapping', value);
}
update(mode: string, code?: string) {
this.mode = mode;
if (code !== undefined) {
@ -155,7 +159,7 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
if (code !== undefined) this.save();
}
render(mode: string, value: string) {
render(mode: string, value: string, options?: EditorConfiguration) {
const root = this.container.find('.data-codeblock-content');
mode = this.getSyntax(mode);
const stage = $(
@ -163,7 +167,10 @@ class CodeBlockEditor implements CodeBlockEditorInterface {
);
root.append(stage);
const pre = stage.find('pre')[0];
this.runMode(value || '', mode, pre, this.getConfig(value, mode));
this.runMode(value || '', mode, pre, {
...this.getConfig(value, mode),
...options,
});
}
save() {

View File

@ -19,14 +19,14 @@
.am-engine .CodeMirror,
.am-engine-view .CodeMirror {
/* \u5b57\u4f53\u53c2\u7167 github\uff0c\u8003\u8651\u548c 14px \u6587\u5b57\u5728\u4e00\u8d77\u6392\u7248\uff0c\u5b57\u53f7\u7528 13px */
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
font-family: monospace;
font-size: 13px;
line-height: 21px;
color: #595959;
direction: ltr;
height: auto;
overflow: hidden;
background: transparent;
}
.am-engine .CodeMirror-lines,
@ -374,15 +374,6 @@
}
.am-engine .CodeMirror,
.am-engine-view .CodeMirror {
position: relative;
overflow: hidden;
height: auto;
background: transparent;
}
.am-engine .CodeMirror-scroll,
.am-engine-view .CodeMirror-scroll {
overflow: scroll !important;
@ -431,7 +422,7 @@
top: 0;
overflow-x: hidden;
overflow-y: scroll;
display: none !important;
display: none;
}
@ -441,7 +432,7 @@
left: 0;
overflow-y: hidden;
overflow-x: scroll;
display: none !important;
display: none;
}
@ -466,7 +457,7 @@
top: 0;
min-height: 100%;
background: #f9f9f9;
z-index: 3;
}
.am-engine .CodeMirror-gutter,
@ -609,6 +600,11 @@
}
.am-engine .CodeMirror-simplescroll-horizontal, .am-engine-view .CodeMirror-simplescroll-horizontal {
bottom: 4px;
cursor: pointer;
}
.am-engine .CodeMirror-measure,
.am-engine-view .CodeMirror-measure {
position: absolute;
@ -748,17 +744,6 @@
}
.am-engine-view .CodeMirror {
padding: 16px;
overflow: initial;
}
.am-engine-view .CodeMirror pre {
padding: 0;
}
.am-content-editor .am-engine .data-codeblock-container .CodeMirror-lines {
min-height: 40px;
}

View File

@ -18,6 +18,7 @@ import './index.css';
export type CodeBlockValue = {
mode?: string;
code?: string;
autoWrap?: boolean;
};
class CodeBlcok extends Card<CodeBlockValue> {
@ -82,10 +83,30 @@ class CodeBlcok extends Card<CodeBlockValue> {
},
});
}
#viewAutoWrap?: boolean = undefined;
toolbar(): Array<CardToolbarItemOptions | ToolbarItemOptions> {
if (!isEngine(this.editor) || this.editor.readonly) {
return [{ type: 'copy' }];
return [
{ type: 'copy' },
{
type: 'switch',
content: this.editor.language.get<string>(
CodeBlcok.cardName,
'autoWrap',
),
getState: () => {
if (this.#viewAutoWrap === undefined) {
this.#viewAutoWrap = !!this.getValue()?.autoWrap;
}
return this.#viewAutoWrap;
},
onClick: () => {
const autoWrap = !this.#viewAutoWrap;
this.#viewAutoWrap = autoWrap;
this.codeEditor?.setAutoWrap(autoWrap);
},
},
];
}
return [
{
@ -117,6 +138,24 @@ class CodeBlcok extends Card<CodeBlockValue> {
}, 100);
},
},
{
type: 'switch',
content: this.editor.language.get<string>(
CodeBlcok.cardName,
'autoWrap',
),
getState: () => {
return !!this.getValue()?.autoWrap;
},
onClick: () => {
const value = this.getValue();
const autoWrap = !value?.autoWrap;
this.setValue({
autoWrap,
});
this.codeEditor?.setAutoWrap(autoWrap);
},
},
];
}
@ -139,13 +178,18 @@ class CodeBlcok extends Card<CodeBlockValue> {
if (isEngine(this.editor)) {
if (this.mirror) {
this.codeEditor.update(mode, code);
this.codeEditor.setAutoWrap(!!value?.autoWrap);
return;
}
setTimeout(() => {
this.mirror = this.codeEditor?.create(mode, code);
this.mirror = this.codeEditor?.create(mode, code, {
lineWrapping: !!value?.autoWrap,
});
}, 50);
} else {
this.codeEditor.render(mode, code);
this.codeEditor?.create(mode, code, {
lineWrapping: !!value?.autoWrap,
});
}
}
}

View File

@ -30,3 +30,5 @@ import 'codemirror/mode/vb/vb';
import 'codemirror/mode/velocity/velocity';
import 'codemirror/mode/xml/xml';
import 'codemirror/mode/yaml/yaml';
import 'codemirror/addon/scroll/simplescrollbars';
import 'codemirror/addon/scroll/simplescrollbars.css';

View File

@ -23,7 +23,8 @@ export interface CodeBlockEditorInterface {
getSyntax(mode: string): string;
create(mode: string, value: string, options?: EditorConfiguration): Editor;
update(mode: string, value?: string): void;
render(mode: string, value: string): void;
setAutoWrap(value: boolean): void;
render(mode: string, value: string, options?: EditorConfiguration): void;
save(): void;
focus(): void;
/**

View File

@ -16,6 +16,7 @@ import {
decodeCardValue,
} from '@aomao/engine';
import CodeBlockComponent, { CodeBlockEditor } from './component';
import locales from './locales';
export interface Options extends PluginOptions {
hotkey?: string | Array<string>;
@ -42,6 +43,7 @@ export default class extends Plugin<Options> {
}
init() {
this.editor.language.add(locales);
this.editor.on('parse:html', (node) => this.parseHtml(node));
this.editor.on('paste:schema', (schema) => this.pasteSchema(schema));
this.editor.on('paste:each', (child) => this.pasteHtml(child));

View File

@ -0,0 +1,5 @@
export default {
codeblock: {
autoWrap: 'Auto Wrap',
},
};

View File

@ -0,0 +1,7 @@
import en from './en-US';
import cn from './zh-CN';
export default {
'en-US': en,
'zh-CN': cn,
};

View File

@ -0,0 +1,5 @@
export default {
codeblock: {
autoWrap: '自动换行',
},
};

View File

@ -94,7 +94,7 @@ export default class FileCard extends Card<FileValue> {
onBeforeRender = (action: 'preview' | 'download', url: string) => {
const filePlugin = this.editor.plugin.components['file'];
if (filePlugin) {
const { onBeforeRender } = filePlugin['options'] || {};
const { onBeforeRender } = (filePlugin['options'] || {}) as any;
if (onBeforeRender) return onBeforeRender(action, url);
}
return url;

View File

@ -263,7 +263,8 @@ class ImageComponent extends Card<ImageValue> {
onBeforeRender: (status, src) => {
const imagePlugin = this.editor.plugin.components['image'];
if (imagePlugin) {
const { onBeforeRender } = imagePlugin['options'] || {};
const { onBeforeRender } = (imagePlugin['options'] ||
{}) as any;
if (onBeforeRender) return onBeforeRender(status, src);
}
return src;

View File

@ -206,7 +206,7 @@ export default class extends Plugin<Options> {
getUrl(value: ImageValue) {
const imagePlugin = this.editor.plugin.components['image'];
if (imagePlugin) {
const { onBeforeRender } = imagePlugin['options'] || {};
const { onBeforeRender } = (imagePlugin['options'] || {}) as any;
if (onBeforeRender) return onBeforeRender(value.status, value.src);
}
return value.src;

View File

@ -160,6 +160,7 @@ class TableComponent extends Card<TableValue> implements TableInterface {
return true;
}
nextIndex++;
return false;
},
);
if (nextTd) {
@ -169,6 +170,7 @@ class TableComponent extends Card<TableValue> implements TableInterface {
}
}
}
return;
});
// 上键选择
this.editor.on('keydown:up', (event) => {
@ -204,6 +206,7 @@ class TableComponent extends Card<TableValue> implements TableInterface {
return true;
}
prevIndex++;
return false;
},
);
if (prevTd) {
@ -213,6 +216,7 @@ class TableComponent extends Card<TableValue> implements TableInterface {
}
}
}
return;
});
}
if (this.colorTool) return;

View File

@ -177,7 +177,11 @@ class VideoComponent extends Card<VideoValue> {
return `
<div class="data-video">
<div class="data-video-content data-video-done"></div>
${videoPlugin && videoPlugin.options.showTitle !== false ? titleElement : ''}
${
videoPlugin && (videoPlugin.options as any).showTitle !== false
? titleElement
: ''
}
</div>
`;
}
@ -185,7 +189,7 @@ class VideoComponent extends Card<VideoValue> {
onBeforeRender = (action: 'query' | 'download' | 'cover', url: string) => {
const videoPlugin = this.editor.plugin.components['video'];
if (videoPlugin) {
const { onBeforeRender } = videoPlugin['options'] || {};
const { onBeforeRender } = (videoPlugin['options'] || {}) as any;
if (onBeforeRender) return onBeforeRender(action, url);
}
return url;