From 815fe267fbd497d9fadc03f6b23fcefea8895f2c Mon Sep 17 00:00:00 2001 From: lastnigtic Date: Tue, 10 Aug 2021 12:51:27 +0800 Subject: [PATCH] feat(adapter): SylApi: support `selectNode` in `setSelection` and return `node` in `getSelection` closes #18 --- docs/en/api.md | 29 ++++++++++++++++++++--------- docs/zh-cn/api.md | 21 +++++++++++++++++---- packages/adapter/src/api.ts | 24 ++++++++++++++++++------ test/case/adapter/sylApi.test.ts | 23 ++++++++++++++++++----- 4 files changed, 73 insertions(+), 24 deletions(-) diff --git a/docs/en/api.md b/docs/en/api.md index cb034e6..6d6afa4 100644 --- a/docs/en/api.md +++ b/docs/en/api.md @@ -58,7 +58,7 @@ Whether the editor is destroyed. ```typescript // types -(config?: {layerType?: string; mergeEmpty?: boolean }) => string | undefined; +(config?: { layerType?: string; mergeEmpty?: boolean }) => string | undefined; ``` Get the content of the editor `html`. @@ -140,16 +140,19 @@ Disable quick input capability. ### getSelection -`getSelection: () => IRangeStatic` +`getSelection: () => IGetSelectionInfo` -Get the current selection, index represents the cursor position; length represents the selected length, pay attention to the non-text length. +Get the current selection, `index` represent the cursor position; `length` represent the selected length, pay attention to the non-text length. +`anchor` represent anchor index of selection, `head` represent head index of selection. Used to distinguish directions. +It will return `node` when selected `node` ### setSelection -`setSelection: (range: IRangeStatic & {scrollIntoView?: boolean }) => void` +`setSelection: (range: IRangeStatic & {scrollIntoView?: boolean, selectNode?: boolean }) => void` Set the selection, `index` represents the cursor position, `length` represents the length of the selection. -Add the `scrollIntoView` parameter to control whether to scroll the selection to the window, the default is `true`. +the `scrollIntoView` parameter to control whether to scroll the selection to the window, the default is `true`. +the `selectNode` parameter to indicates whether to select the `node`. ### getText @@ -184,8 +187,7 @@ nodesBetween: ( Used to traverse the document -- The `walker` function is used to traverse the document, returning `true` to perform a deep traversal on the current node, and returning `false` to jump out of the current node, and the default deep traversal. --`range` is the range to be traversed. To traverse the full text, you can pass in `{ index: 0, length: SylApi.length }`, and the default range is the current selection. +- The `walker` function is used to traverse the document, returning `true` to perform a deep traversal on the current node, and returning `false` to jump out of the current node, and the default deep traversal. -`range` is the range to be traversed. To traverse the full text, you can pass in `{ index: 0, length: SylApi.length }`, and the default range is the current selection. ### insert @@ -334,6 +336,15 @@ Decorate existing nodes. } ``` +### IGetSelectionInfo + +````typescript +interface IGetSelectionInfo extends Types.IRangeStatic { + anchor: number; + head: number; + node?: ProsemirrorNode; +} + ### InsertOption ```typescript @@ -344,7 +355,7 @@ Decorate existing nodes. focus?: boolean; // focus, the default is true inheritMarks?: boolean; // Whether to inherit the current style when inserting in-line nodes, the default is true } -``` +```` ### IUpdateOption @@ -410,4 +421,4 @@ EventChannel: { If `SylApi` cannot meet the demand, the following methods can be used: - Through [Issues](https://github.com/bytedance/syllepsis/issues), provide requirements and application scenarios. After confirming the support, we will schedule development or the demand side will meet it through `Pull Request`. -- You can get the native `EditorView` object through the `view` property mounted on the editor instance to perform lower-level operations. For details, please refer to [Prosemirror](https://prosemirror.net/docs/ref/). \ No newline at end of file +- You can get the native `EditorView` object through the `view` property mounted on the editor instance to perform lower-level operations. For details, please refer to [Prosemirror](https://prosemirror.net/docs/ref/). diff --git a/docs/zh-cn/api.md b/docs/zh-cn/api.md index 1643b90..309918d 100644 --- a/docs/zh-cn/api.md +++ b/docs/zh-cn/api.md @@ -138,16 +138,19 @@ ### getSelection -`getSelection: () => IRangeStatic` +`getSelection: () => IGetSelectionInfo` -获取当前选区,index 代表光标位置;length 代表选中长度,注意非文本长度。 +获取当前选区,`index` 代表光标位置;`length` 代表选中长度,注意非文本长度。 +`anchor`为锚点位置,`head`为终止位置,用于区分选中方向 +当选中节点时返回选中的`node` ### setSelection -`setSelection: (range: IRangeStatic & { scrollIntoView?: boolean }) => void` +`setSelection: (range: IRangeStatic & { scrollIntoView?: boolean, selectNode?: boolean }) => void` 设置选区,`index`代表光标位置,`length`代表选区长度。 -增加`scrollIntoView`参数,控制是否将选区滚动到视窗,默认为`true`。 +`scrollIntoView`参数,控制是否将选区滚动到视窗,默认为`true`。 +`selectNode`参数,控制是否选中节点,默认为 `false` ### getText @@ -332,6 +335,16 @@ nodesBetween: ( } ``` +### IGetSelectionInfo + +```typescript +interface IGetSelectionInfo extends Types.IRangeStatic { + anchor: number; + head: number; + node?: ProsemirrorNode; +} +``` + ### InsertOption ```typescript diff --git a/packages/adapter/src/api.ts b/packages/adapter/src/api.ts index 7114cd8..59ba8bc 100755 --- a/packages/adapter/src/api.ts +++ b/packages/adapter/src/api.ts @@ -1,6 +1,6 @@ import { redo } from 'prosemirror-history'; import { DOMParser, Node as ProsemirrorNode } from 'prosemirror-model'; -import { EditorState, TextSelection } from 'prosemirror-state'; +import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { IDecoState } from './basic/decoration'; @@ -56,10 +56,17 @@ interface ISetHTMLOptions { keepWhiteSpace?: boolean; } +interface IGetSelectionInfo extends Types.IRangeStatic { + anchor: number; + head: number; + node?: ProsemirrorNode; +} + interface ISetSelectionOptions extends Partial { anchor?: number; head?: number; scrollIntoView?: boolean; + selectNode?: boolean; } type TSylApiCommand = (...args: any[]) => any; @@ -267,18 +274,21 @@ class SylApi { } public getSelection = () => { - // anchor, head can - const { from, to, anchor, head } = getRealSelectionInfo(this.view.state.selection); - return { + const selection = this.view.state.selection; + const { from, to, anchor, head } = getRealSelectionInfo(selection); + const selectionInfo: IGetSelectionInfo = { index: from, length: to - from, anchor, head, }; + if (selection instanceof NodeSelection) selectionInfo.node = selection.node; + + return selectionInfo; }; public setSelection(config: ISetSelectionOptions) { - const { index, length = 0, scrollIntoView = true, anchor, head } = config; + const { index, length = 0, scrollIntoView = true, anchor, head, selectNode = false } = config; if (index === undefined && anchor === undefined && head === undefined) { throw new TypeError('must provide one of these parameters: [index, anchor, head]'); } @@ -298,7 +308,9 @@ class SylApi { } } - const selection = TextSelection.create(doc, resultAnchor, resultHead); + const selection = selectNode + ? NodeSelection.create(doc, resultAnchor) + : TextSelection.create(doc, resultAnchor, resultHead); const tr = state.tr.setSelection(selection); dispatch(scrollIntoView === false ? tr : tr.scrollIntoView()); } diff --git a/test/case/adapter/sylApi.test.ts b/test/case/adapter/sylApi.test.ts index c115396..f78a51f 100755 --- a/test/case/adapter/sylApi.test.ts +++ b/test/case/adapter/sylApi.test.ts @@ -129,6 +129,15 @@ describe('single API test', () => { expect(count).toEqual(1); }); + test('can setSelection to select node', async () => { + const nodeName = await page.evaluate(() => { + editor.setHTML(CARD_HTML); + editor.setSelection({ index: 0, selectNode: true }); + return editor.getSelection().node.type.name; + }); + expect(nodeName).toBe('card'); + }); + test('it can setContent', async () => { const html = await page.evaluate(() => { const json = { @@ -678,23 +687,27 @@ describe('insert API test', () => { const res1 = await page.evaluate(() => { editor.setHTML(''); editor.insertCard('card', {}, { replaceEmpty: true }); + return { html: editor.getHTML(), selection: editor.getSelection(), }; }); + expect(res1.html).toBe(CardView()); expect(res1.selection).toMatchObject({ index: 2, length: 0 }); const res2 = await page.evaluate(() => { editor.setHTML(`

${CARD_HTML}`); editor.setSelection({ index: 1, scrollIntoView: false }); - editor.insertCard('card'); - return { - html: editor.getHTML(), - selection: editor.getSelection(), - }; + + return JSON.parse( + JSON.stringify({ + html: editor.getHTML(), + selection: editor.getSelection(), + }), + ); }); expect(res2.html).toBe(`${CardView()}${CardView()}`);