Skip to content

Commit

Permalink
daily commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Xheldon committed Jan 19, 2020
1 parent da41085 commit 8f8acb7
Show file tree
Hide file tree
Showing 17 changed files with 259 additions and 82 deletions.
1 change: 1 addition & 0 deletions app/commands.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// forward the default export of module's command
export { default as insertlist } from '@modules/list/commands/insertlist';
export { default as insertparagraph } from '@modules/paragraph/commands/insertparagraph';
7 changes: 4 additions & 3 deletions app/components/commons/View.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { EditorState } from 'prosemirror-state';
import { ReactElement } from 'react';

import { ReactViewType } from '@interfaces';
import { rdxDispatch } from '@redux/store';
import store, { rdxDispatch } from '@redux/store';
import { showPopup } from '@redux/actions';
import { ReactView } from '@abstract/ReactView';
import { StateType } from '@redux/interface';

export class View extends ReactView<EditorView, EditorState> {
type: string;
component: (view: EditorView, prevState: EditorState) => ReactElement;
component: (view: EditorView, prevState: EditorState, reduxState: StateType) => ReactElement;
view: EditorView;
options: object;
constructor(props: ReactViewType) {
Expand All @@ -25,7 +26,7 @@ export class View extends ReactView<EditorView, EditorState> {

// pass the view to calculate the popup's position
let updateComponent: ReactElement;
updateComponent = this.component(view, prevState);
updateComponent = this.component(view, prevState, store.getState());
rdxDispatch(showPopup({
type: this.type,
component: updateComponent,
Expand Down
21 changes: 14 additions & 7 deletions app/components/slash/slash-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';

import { slashPopupPluginKey } from '@modules/slash/plugin';
import { insertlist } from '@commands';
import { insertlist, insertparagraph } from '@commands';
import { StateType } from '@redux/interface';

import './style.scss';
import { act } from 'react-dom/test-utils';

// make a list which has a title property to filter, a content to show and a handler to response;

Expand All @@ -16,10 +16,10 @@ const actionList = [
title: 'unorderlist',
content(): ReactElement {
return (
<div className={'n-insert-text'}>插入文本</div>
<div className={'n-insert-ul'}>插入列表</div>
);
},
handler: insertlist
handler: insertlist,
/*
handler({ view, options }: { view: EditorView, options: {start: number, end: number} }) {
// invoke the relate command
Expand All @@ -35,10 +35,16 @@ const actionList = [
});
}
*/
}, {
title: 'paragraph',
content(): ReactElement {
return <div className={'n-insert-p'}>插入文本</div>
},
handler: insertparagraph
}
];

function SlashPopupView(view: EditorView, prevState: EditorState): ReactElement {
function SlashPopupView(view: EditorView, prevState: EditorState, reduxState: StateType): ReactElement {
let state = view.state; // new state;
if (
prevState && prevState.doc.eq(state.doc)
Expand All @@ -55,9 +61,10 @@ function SlashPopupView(view: EditorView, prevState: EditorState): ReactElement
top: pos.bottom + 'px'
};
let filterText = meta.filterText && meta.filterText.slice(1) || '';
let childList: ReactElement[] = actionList.map((arg) => {
let childList: ReactElement[] = actionList.map((arg, k) => {
if ((new RegExp(`${filterText}`, 'g')).test(arg.title)) {
return <div key={arg.title} onClick={arg.handler.bind(null, {
const isActive = k === 0 || reduxState.popup.options.currentSelect === arg.title;
return <div className={isActive ? 'active' : ''} key={arg.title} onClick={arg.handler.bind(null, {
view,
options: {
start: meta.start,
Expand Down
104 changes: 77 additions & 27 deletions app/keymap.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,76 @@
import { EditorState, Selection, TextSelection } from "prosemirror-state";
import { EditorState, Selection, TextSelection, NodeSelection, AllSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";

import { isInTextBlock } from '@utils/module-location';
import { ResolvedPos } from "prosemirror-model";

import { isInTextBlock, isInCodeBlock, isInList } from '@utils/module-location';
import { findLastTextblockPosInPreviousSiblingBlockNode } from '@utils/module-position';
import { getSlashStatus, getState } from '@redux/selector';
import { rdxDispatch } from '@redux/store';
import { selectInsert } from '@redux/actions';

export default {
Enter: (state: EditorState, dispatch: Function, view: EditorView) => {
const selection: Selection = state.selection;
const $from: ResolvedPos = selection.$from;
const $to: ResolvedPos = selection.$to;
const tr = state.tr;
// 如果在非 list 中, 则在当前节点同级直接插入一个新的 paragraph
// 需要考虑光标在的位置之后是否有内容, 有的话需要将其放入新 paragraph 中
if (isInTextBlock(state)) {
// 获取到当前文本块节点的父级的后面位置
const insertPos = $from.after(-1);
// 获取当前文本节点的最后位置
const end = $from.end();
// 得到当前选区结束位置到最后的部分生成 slice
const slice = state.doc.slice($to.pos, end);
// 构建一个 paragraph, 将 slice 的 content 当做其内容, 此处可以直接写 create, 也可以使用 findWrapper 工具函数
// 二者的属性都使用默认值
const node = state.schema.nodes.p.create(null, state.schema.nodes.textBlock.create(null, slice.content));
// 然后删除从 from 到 end 的内容
tr.delete($from.pos, end);
// 因为之前的 delete 操作, 需要 mapping pos
const mappedPos = tr.mapping.map(insertPos);
// 插入
tr.insert(mappedPos, node);
// 此时已经插入成功, 但是光标还在上面, 因此移动这个光标(新建一个 TextSelection 即可)
tr.setSelection(TextSelection.create(tr.doc, mappedPos + 2));
dispatch(tr.scrollIntoView());
return true;
// 获取 nodesType 映射
const _nodes = state.schema.nodes;
if (getSlashStatus()) {
// slash 出现的时候是选择插入状态, 按下 enter 的时候一定是光标状态, 且一定在 textblock 中, 因此按下 enter 会插入不同类型的 block
console.log('getState:', getState());
} else {
// 否则理论上应该会有其他模块处理选区, 当前选区应该是 nodeblock
return false;
// 如果在非 list 中, 则在当前节点同级直接插入一个新的 paragraph
// 需要考虑光标在的位置之后是否有内容, 有的话需要将其放入新 paragraph 中
if (isInTextBlock(state)) {
// 获取到当前文本块节点的父级的后面位置
const insertPos = $from.after(-1);
// 获取当前文本节点的最后位置
const end = $from.end();
// 得到当前选区结束位置到最后的部分生成 slice
const slice = state.doc.slice($to.pos, end);
const currentNode = $from.node(-1);
let insertNode = null;
if (isInCodeBlock(state)) {
tr.delete($from.pos, $to.pos);
// 在 codeblock 中, 仅换行即可, 因此插入一个换行符
tr.insert(tr.mapping.map($to.pos), _nodes.text.create(null, '\n'));
} else {
if (isInList(state)) {
// 在 ul 中, 换行后在下一行插入一个 li
// 在 ol 中, 换行后在下一行插入一个 li, 同时读取当前 li 的 order 属性, 并 +1
// 在 todo 中, 换行后在下一行插入一个 li
// NOTE: 因为插入后的节点跟当前节点是一个类型, 因此也可以考虑插入一个 slice, 设置好 open 和 close 的深度即可, 此处常规处理, 即找到插入点, 插入一个 node
if (currentNode.type !== _nodes.ol) {
insertNode = _nodes[currentNode.type.name].create(null, _nodes.textBlock.create(null, slice.content));
} else {
const currentOrder = currentNode.attrs.order || 1;
insertNode = _nodes.ol.create(null, _nodes.textBlock.create({
order: currentOrder + 1
}, slice.content));
}
} else {
// 此时应该位于普通非原子节点的 block 中, 如 blockquote, heading 等
// 构建一个 paragraph, 将 slice 的 content 当做其内容, 此处可以直接写 create, 也可以使用 findWrapper 工具函数
// 二者的属性都使用默认值
insertNode = _nodes.p.create(null, _nodes.textBlock.create(null, slice.content));
}
// 然后删除从 from 到 end 的内容
tr.delete($from.pos, end);
// 因为之前的 delete 操作, 需要 mapping pos
const mappedPos = tr.mapping.map(insertPos);
// 插入
tr.insert(mappedPos, insertNode);
// 此时已经插入成功, 但是光标还在上面, 因此移动这个光标(新建一个 TextSelection 即可)
tr.setSelection(TextSelection.create(tr.doc, mappedPos + 2));
dispatch(tr.scrollIntoView());
return true;
}
} else if (selection instanceof NodeSelection || selection instanceof AllSelection) {
// 否则理论上应该会有其他模块处理选区, 当前选区应该是 nodeblock, 则在其下插入一个 paragraph
tr.insert($to.pos +2, _nodes.p.create(null, _nodes.textBlock.create(null, _nodes.text.create(null, ''))));
return true;
}
}
return false;
},
Expand All @@ -47,6 +82,7 @@ export default {
const $from = selection.$from;
const $to = selection.$to;
const tr = state.tr;
// 如果选区首尾都在 textblock 中
if (isInTextBlock(state)) {
// 如果是光标, 则删除前一个位置
if ((<TextSelection>selection).$cursor) {
Expand All @@ -71,5 +107,19 @@ export default {
} else {

}
},
ArrowUp: (state: EditorState, dispatch: Function, view: EditorView) => {
// TODO: 当 slash 出现的时候, 操作选项框
if (getSlashStatus()) {
rdxDispatch(selectInsert({
type: 'SELECT_SLASH_POPUP',
options: {
currentSelect: 'insertparagraph'
}
}));
}
},
ArrowDown: (state: EditorState, dispatch: Function, view: EditorView) => {
// TODO: 当 slash 出现的时候, 操作选项框
}
}
14 changes: 12 additions & 2 deletions app/modules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,24 @@ section 是一个可以包含 block 的 div, 主要用途是在水平分隔多
</div>
</div>
```
## 其他非原子元素(heading/blockquote/codeblock)
## 非原子元素

### heading/blockquote/codeblock

```html
<div type="block-type">
<div>一些文本</div>
</div>
```

### table

table 也是用 div 模拟. TODO

### media(video/audio)

预计使用 embeded 元素实现, 嵌入三方视频需要商业付费, TODO

## 原子元素

### image(不支持行内 image, 行内的请使用 emoji)
Expand Down Expand Up @@ -104,7 +114,7 @@ section 是一个可以包含 block 的 div, 主要用途是在水平分隔多

# keymap

keymap 文件定义了整个编辑器的功能键的交互行为, 此处统一处理, 不再将文件分散到各个 module 通过返回 true/false 来处理了.
keymap 文件定义了整个编辑器的功能键的交互行为, 此处统一处理, 不再将文件分散到各个 module 通过返回 true/false 来处理.

## Enter

Expand Down
2 changes: 1 addition & 1 deletion app/modules/list/commands/insertlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { findWrapping, ReplaceAroundStep } from 'prosemirror-transform';
import { Fragment, Slice } from 'prosemirror-model';
import { insertlist } from '../../../commands';

export default ({ name = 'insertlist', value = '', view, options}: CommandArgType): void => {
export default ({ name = 'insertlist', view, options = {}}: CommandArgType): void => {
// TODO: insert a list, distinguish by options, the options has a type property which indicate the type of insert of the list, like order, unorder, todo, etc
// the command be invoked only have one way(by now): by slash, so we should delete the range from slash to the last typed character before insert the list and then insert the list in the slash pos(the options.start pos)
// remember we may should mapped the all content after the insert pos
Expand Down
30 changes: 30 additions & 0 deletions app/modules/list/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,36 @@ export const ul = {
group: 'block',
defining: true,
content: 'textBlock block*',
toDOM(node: Node): DOMOutputSpec {
//TODO: only put partial attrs on dom
return [
'div',
node.attrs,
[
'div', '·'
],
[
'div', 0
]
]
}
};

export const ol = {
attrs: {
class: {
default: 'n-ordered-list'
},
type: {
default: 'order-list'
},
style: {
default: ''
}
},
group: 'block',
defining: true,
content: 'textBlock block*',
toDOM(node: Node): DOMOutputSpec {
//TODO: only put partial attrs on dom
return [
Expand Down
34 changes: 34 additions & 0 deletions app/modules/paragraph/commands/insertparagraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { TextSelection } from 'prosemirror-state';

import { CommandArgType } from "@interfaces";
import { isInCodeBlock, isInList } from '@utils/module-location';

export default ({ name = 'insertparagraph', view, options }: CommandArgType): void => {
const {
state,
state: {
schema,
selection: {
$from,
$to
},
tr,
},
dispatch
} = view;
const _nodes = state.schema.nodes;
// 获取到当前文本块节点的父级的后面位置
// TODO: 未考虑复杂情况, 如在 table 内部的时候
const insertPos = $from.after(-1);
const { inputStart, inputEnd } = options;
// 删除 start 到 end 输入的内容从而确认 enter 操作
tr.delete(inputStart, inputEnd);
// 构建一个空的 paragraph
let insertNode = _nodes.p.create(null, _nodes.textBlock.create(null, _nodes.text.create(null, '')));
let mappedPos = tr.mapping.map(insertPos);
// 插入 mapping 后的位置
tr.insert(mappedPos, insertNode);
// 此时已经插入成功, 但是光标还在上面, 因此移动这个光标(新建一个 TextSelection 即可)
tr.setSelection(TextSelection.create(tr.doc, mappedPos + 2));
dispatch(tr.scrollIntoView());
}
16 changes: 14 additions & 2 deletions app/redux/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ActionType, PopupType } from '@interfaces';
import { ActionType, PopupType, SelectType } from '@interfaces';

export const SHOW_SLASH_POPUP = 'SHOW_SLASH_POPUP';
export const SELECT_SLASH_POPUP = 'SELECT_SLASH_POPUP';

export const showPopup = (props: PopupType): ActionType => {
const { type, component, view, options } = props;
Expand All @@ -9,7 +10,18 @@ export const showPopup = (props: PopupType): ActionType => {
payload: {
component,
view,
options,
type
}
}
};

export const selectInsert = (props: SelectType): ActionType => {
const { type, options } = props;
return {
type,
payload: {
options
}
}
};
}
15 changes: 10 additions & 5 deletions app/redux/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,24 @@ export interface StateType {
export interface PopupStateType {
component?: ReactElement;
view?: EditorView;
options?: object;
options?: any;
}

export interface ReactViewType {
type: string;
component: (view: EditorView, prevState: EditorState) => ReactElement;
component: (view: EditorView, prevState: EditorState, reduxState: StateType) => ReactElement;
view: EditorView;
options?: object;
options?: any;
}

export interface PopupType {
type: string;
component: ReactElement;
view?: EditorView;
options?: object;
}
options?: any;
}

export interface SelectType {
type: string;
options?: any
}
Loading

0 comments on commit 8f8acb7

Please sign in to comment.