common.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import {
  2. BlockData,
  3. BlockType,
  4. DocumentState,
  5. NestedBlock,
  6. RangeSelectionState,
  7. TextDelta,
  8. TextSelection,
  9. } from '$app/interfaces/document';
  10. import { Descendant, Element, Text } from 'slate';
  11. import { BlockPB } from '@/services/backend';
  12. import { Log } from '$app/utils/log';
  13. import { nanoid } from 'nanoid';
  14. import { clone } from '$app/utils/tool';
  15. export function slateValueToDelta(slateNodes: Descendant[]) {
  16. const element = slateNodes[0] as Element;
  17. const children = element.children as Text[];
  18. return children.map((child) => {
  19. const { text, ...attributes } = child;
  20. return {
  21. insert: text,
  22. attributes,
  23. };
  24. });
  25. }
  26. export function deltaToSlateValue(delta: TextDelta[]) {
  27. const slateNode = {
  28. type: 'paragraph',
  29. children: [{ text: '' }],
  30. };
  31. const slateNodes = [slateNode];
  32. if (delta.length > 0) {
  33. slateNode.children = delta.map((d) => {
  34. return {
  35. ...d.attributes,
  36. text: d.insert,
  37. };
  38. });
  39. }
  40. return slateNodes;
  41. }
  42. export function getDeltaFromSlateNodes(slateNodes: Descendant[]) {
  43. const element = slateNodes[0] as Element;
  44. const children = element.children as Text[];
  45. return children.map((child) => {
  46. const { text, ...attributes } = child;
  47. return {
  48. insert: text,
  49. attributes,
  50. };
  51. });
  52. }
  53. export function blockPB2Node(block: BlockPB) {
  54. let data = {};
  55. try {
  56. data = JSON.parse(block.data);
  57. } catch {
  58. Log.error('[Document Open] json parse error', block.data);
  59. }
  60. const node = {
  61. id: block.id,
  62. type: block.ty as BlockType,
  63. parent: block.parent_id,
  64. children: block.children_id,
  65. data,
  66. };
  67. return node;
  68. }
  69. export function generateId() {
  70. return nanoid(10);
  71. }
  72. export function getPrevLineId(state: DocumentState, id: string) {
  73. const node = state.nodes[id];
  74. if (!node.parent) return;
  75. const parent = state.nodes[node.parent];
  76. const children = state.children[parent.children];
  77. const index = children.indexOf(id);
  78. const prevNodeId = children[index - 1];
  79. const prevNode = state.nodes[prevNodeId];
  80. if (!prevNode) {
  81. return parent.id;
  82. }
  83. // find prev line
  84. let prevLineId = prevNode.id;
  85. while (prevLineId) {
  86. const prevLineChildren = state.children[state.nodes[prevLineId].children];
  87. if (prevLineChildren.length === 0) break;
  88. prevLineId = prevLineChildren[prevLineChildren.length - 1];
  89. }
  90. return prevLineId || parent.id;
  91. }
  92. export function getNextLineId(state: DocumentState, id: string) {
  93. const node = state.nodes[id];
  94. if (!node.parent) return;
  95. const firstChild = state.children[node.children][0];
  96. if (firstChild) return firstChild;
  97. let nextNodeId = getNextNodeId(state, id);
  98. let parent: NestedBlock | null = state.nodes[node.parent];
  99. while (!nextNodeId && parent) {
  100. nextNodeId = getNextNodeId(state, parent.id);
  101. parent = parent.parent ? state.nodes[parent.parent] : null;
  102. }
  103. return nextNodeId;
  104. }
  105. export function getNextNodeId(state: DocumentState, id: string) {
  106. const node = state.nodes[id];
  107. if (!node.parent) return;
  108. const parent = state.nodes[node.parent];
  109. const children = state.children[parent.children];
  110. const index = children.indexOf(id);
  111. const nextNodeId = children[index + 1];
  112. return nextNodeId;
  113. }
  114. export function getPrevNodeId(state: DocumentState, id: string) {
  115. const node = state.nodes[id];
  116. if (!node.parent) return;
  117. const parent = state.nodes[node.parent];
  118. const children = state.children[parent.children];
  119. const index = children.indexOf(id);
  120. const prevNodeId = children[index - 1];
  121. return prevNodeId;
  122. }
  123. export function newBlock<Type>(type: BlockType, parentId: string, data: BlockData<Type>): NestedBlock<Type> {
  124. return {
  125. id: generateId(),
  126. type,
  127. parent: parentId,
  128. children: generateId(),
  129. data,
  130. };
  131. }
  132. export function getCollapsedRange(id: string, selection: TextSelection): RangeSelectionState {
  133. const point = {
  134. id,
  135. selection,
  136. };
  137. return {
  138. anchor: clone(point),
  139. focus: clone(point),
  140. isDragging: false,
  141. selection: [],
  142. };
  143. }
  144. export function iterateNodes(
  145. range: {
  146. startId: string;
  147. endId: string;
  148. },
  149. isForward: boolean,
  150. document: DocumentState,
  151. callback: (nodeId?: string) => boolean
  152. ) {
  153. const { startId, endId } = range;
  154. let currentId = startId;
  155. while (currentId && currentId !== endId) {
  156. if (isForward) {
  157. currentId = getNextLineId(document, currentId) || '';
  158. } else {
  159. currentId = getPrevLineId(document, currentId) || '';
  160. }
  161. if (callback(currentId)) {
  162. break;
  163. }
  164. }
  165. }
  166. export function getNodesInRange(
  167. range: {
  168. startId: string;
  169. endId: string;
  170. },
  171. isForward: boolean,
  172. document: DocumentState
  173. ) {
  174. const nodeIds: string[] = [];
  175. nodeIds.push(range.startId);
  176. iterateNodes(range, isForward, document, (nodeId) => {
  177. if (nodeId) {
  178. nodeIds.push(nodeId);
  179. return false;
  180. } else {
  181. return true;
  182. }
  183. });
  184. nodeIds.push(range.endId);
  185. return nodeIds;
  186. }
  187. export function nodeInRange(
  188. id: string,
  189. range: {
  190. startId: string;
  191. endId: string;
  192. },
  193. isForward: boolean,
  194. document: DocumentState
  195. ) {
  196. let match = false;
  197. iterateNodes(range, isForward, document, (nodeId) => {
  198. if (nodeId === id) {
  199. match = true;
  200. return true;
  201. }
  202. return false;
  203. });
  204. return match;
  205. }