4dde4a38b1a32036765caf66e79e8103c6b831a4715011ca1f51716f35f3529f88e858ea1073795a89eb3fea9ccafa4f6f5d61e82908750c36d3b81735e83c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. import { cloneDeep, isEqual } from 'lodash-es';
  2. import Delta, { AttributeMap } from 'quill-delta';
  3. import { EmbedBlot, Scope, TextBlot } from 'parchment';
  4. import Quill from '../core/quill.js';
  5. import logger from '../core/logger.js';
  6. import Module from '../core/module.js';
  7. const debug = logger('quill:keyboard');
  8. const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';
  9. class Keyboard extends Module {
  10. static match(evt, binding) {
  11. if (['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(key => {
  12. return !!binding[key] !== evt[key] && binding[key] !== null;
  13. })) {
  14. return false;
  15. }
  16. return binding.key === evt.key || binding.key === evt.which;
  17. }
  18. constructor(quill, options) {
  19. super(quill, options);
  20. this.bindings = {};
  21. // @ts-expect-error Fix me later
  22. Object.keys(this.options.bindings).forEach(name => {
  23. // @ts-expect-error Fix me later
  24. if (this.options.bindings[name]) {
  25. // @ts-expect-error Fix me later
  26. this.addBinding(this.options.bindings[name]);
  27. }
  28. });
  29. this.addBinding({
  30. key: 'Enter',
  31. shiftKey: null
  32. }, this.handleEnter);
  33. this.addBinding({
  34. key: 'Enter',
  35. metaKey: null,
  36. ctrlKey: null,
  37. altKey: null
  38. }, () => {});
  39. if (/Firefox/i.test(navigator.userAgent)) {
  40. // Need to handle delete and backspace for Firefox in the general case #1171
  41. this.addBinding({
  42. key: 'Backspace'
  43. }, {
  44. collapsed: true
  45. }, this.handleBackspace);
  46. this.addBinding({
  47. key: 'Delete'
  48. }, {
  49. collapsed: true
  50. }, this.handleDelete);
  51. } else {
  52. this.addBinding({
  53. key: 'Backspace'
  54. }, {
  55. collapsed: true,
  56. prefix: /^.?$/
  57. }, this.handleBackspace);
  58. this.addBinding({
  59. key: 'Delete'
  60. }, {
  61. collapsed: true,
  62. suffix: /^.?$/
  63. }, this.handleDelete);
  64. }
  65. this.addBinding({
  66. key: 'Backspace'
  67. }, {
  68. collapsed: false
  69. }, this.handleDeleteRange);
  70. this.addBinding({
  71. key: 'Delete'
  72. }, {
  73. collapsed: false
  74. }, this.handleDeleteRange);
  75. this.addBinding({
  76. key: 'Backspace',
  77. altKey: null,
  78. ctrlKey: null,
  79. metaKey: null,
  80. shiftKey: null
  81. }, {
  82. collapsed: true,
  83. offset: 0
  84. }, this.handleBackspace);
  85. this.listen();
  86. }
  87. addBinding(keyBinding) {
  88. let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  89. let handler = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  90. const binding = normalize(keyBinding);
  91. if (binding == null) {
  92. debug.warn('Attempted to add invalid keyboard binding', binding);
  93. return;
  94. }
  95. if (typeof context === 'function') {
  96. context = {
  97. handler: context
  98. };
  99. }
  100. if (typeof handler === 'function') {
  101. handler = {
  102. handler
  103. };
  104. }
  105. const keys = Array.isArray(binding.key) ? binding.key : [binding.key];
  106. keys.forEach(key => {
  107. const singleBinding = {
  108. ...binding,
  109. key,
  110. ...context,
  111. ...handler
  112. };
  113. this.bindings[singleBinding.key] = this.bindings[singleBinding.key] || [];
  114. this.bindings[singleBinding.key].push(singleBinding);
  115. });
  116. }
  117. listen() {
  118. this.quill.root.addEventListener('keydown', evt => {
  119. if (evt.defaultPrevented || evt.isComposing) return;
  120. // evt.isComposing is false when pressing Enter/Backspace when composing in Safari
  121. // https://bugs.webkit.org/show_bug.cgi?id=165004
  122. const isComposing = evt.keyCode === 229 && (evt.key === 'Enter' || evt.key === 'Backspace');
  123. if (isComposing) return;
  124. const bindings = (this.bindings[evt.key] || []).concat(this.bindings[evt.which] || []);
  125. const matches = bindings.filter(binding => Keyboard.match(evt, binding));
  126. if (matches.length === 0) return;
  127. // @ts-expect-error
  128. const blot = Quill.find(evt.target, true);
  129. if (blot && blot.scroll !== this.quill.scroll) return;
  130. const range = this.quill.getSelection();
  131. if (range == null || !this.quill.hasFocus()) return;
  132. const [line, offset] = this.quill.getLine(range.index);
  133. const [leafStart, offsetStart] = this.quill.getLeaf(range.index);
  134. const [leafEnd, offsetEnd] = range.length === 0 ? [leafStart, offsetStart] : this.quill.getLeaf(range.index + range.length);
  135. const prefixText = leafStart instanceof TextBlot ? leafStart.value().slice(0, offsetStart) : '';
  136. const suffixText = leafEnd instanceof TextBlot ? leafEnd.value().slice(offsetEnd) : '';
  137. const curContext = {
  138. collapsed: range.length === 0,
  139. // @ts-expect-error Fix me later
  140. empty: range.length === 0 && line.length() <= 1,
  141. format: this.quill.getFormat(range),
  142. line,
  143. offset,
  144. prefix: prefixText,
  145. suffix: suffixText,
  146. event: evt
  147. };
  148. const prevented = matches.some(binding => {
  149. if (binding.collapsed != null && binding.collapsed !== curContext.collapsed) {
  150. return false;
  151. }
  152. if (binding.empty != null && binding.empty !== curContext.empty) {
  153. return false;
  154. }
  155. if (binding.offset != null && binding.offset !== curContext.offset) {
  156. return false;
  157. }
  158. if (Array.isArray(binding.format)) {
  159. // any format is present
  160. if (binding.format.every(name => curContext.format[name] == null)) {
  161. return false;
  162. }
  163. } else if (typeof binding.format === 'object') {
  164. // all formats must match
  165. if (!Object.keys(binding.format).every(name => {
  166. // @ts-expect-error Fix me later
  167. if (binding.format[name] === true) return curContext.format[name] != null;
  168. // @ts-expect-error Fix me later
  169. if (binding.format[name] === false) return curContext.format[name] == null;
  170. // @ts-expect-error Fix me later
  171. return isEqual(binding.format[name], curContext.format[name]);
  172. })) {
  173. return false;
  174. }
  175. }
  176. if (binding.prefix != null && !binding.prefix.test(curContext.prefix)) {
  177. return false;
  178. }
  179. if (binding.suffix != null && !binding.suffix.test(curContext.suffix)) {
  180. return false;
  181. }
  182. // @ts-expect-error Fix me later
  183. return binding.handler.call(this, range, curContext, binding) !== true;
  184. });
  185. if (prevented) {
  186. evt.preventDefault();
  187. }
  188. });
  189. }
  190. handleBackspace(range, context) {
  191. // Check for astral symbols
  192. const length = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(context.prefix) ? 2 : 1;
  193. if (range.index === 0 || this.quill.getLength() <= 1) return;
  194. let formats = {};
  195. const [line] = this.quill.getLine(range.index);
  196. let delta = new Delta().retain(range.index - length).delete(length);
  197. if (context.offset === 0) {
  198. // Always deleting newline here, length always 1
  199. const [prev] = this.quill.getLine(range.index - 1);
  200. if (prev) {
  201. const isPrevLineEmpty = prev.statics.blotName === 'block' && prev.length() <= 1;
  202. if (!isPrevLineEmpty) {
  203. // @ts-expect-error Fix me later
  204. const curFormats = line.formats();
  205. const prevFormats = this.quill.getFormat(range.index - 1, 1);
  206. formats = AttributeMap.diff(curFormats, prevFormats) || {};
  207. if (Object.keys(formats).length > 0) {
  208. // line.length() - 1 targets \n in line, another -1 for newline being deleted
  209. const formatDelta = new Delta()
  210. // @ts-expect-error Fix me later
  211. .retain(range.index + line.length() - 2).retain(1, formats);
  212. delta = delta.compose(formatDelta);
  213. }
  214. }
  215. }
  216. }
  217. this.quill.updateContents(delta, Quill.sources.USER);
  218. this.quill.focus();
  219. }
  220. handleDelete(range, context) {
  221. // Check for astral symbols
  222. const length = /^[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(context.suffix) ? 2 : 1;
  223. if (range.index >= this.quill.getLength() - length) return;
  224. let formats = {};
  225. const [line] = this.quill.getLine(range.index);
  226. let delta = new Delta().retain(range.index).delete(length);
  227. // @ts-expect-error Fix me later
  228. if (context.offset >= line.length() - 1) {
  229. const [next] = this.quill.getLine(range.index + 1);
  230. if (next) {
  231. // @ts-expect-error Fix me later
  232. const curFormats = line.formats();
  233. const nextFormats = this.quill.getFormat(range.index, 1);
  234. formats = AttributeMap.diff(curFormats, nextFormats) || {};
  235. if (Object.keys(formats).length > 0) {
  236. delta = delta.retain(next.length() - 1).retain(1, formats);
  237. }
  238. }
  239. }
  240. this.quill.updateContents(delta, Quill.sources.USER);
  241. this.quill.focus();
  242. }
  243. handleDeleteRange(range) {
  244. deleteRange({
  245. range,
  246. quill: this.quill
  247. });
  248. this.quill.focus();
  249. }
  250. handleEnter(range, context) {
  251. const lineFormats = Object.keys(context.format).reduce((formats, format) => {
  252. if (this.quill.scroll.query(format, Scope.BLOCK) && !Array.isArray(context.format[format])) {
  253. formats[format] = context.format[format];
  254. }
  255. return formats;
  256. }, {});
  257. const delta = new Delta().retain(range.index).delete(range.length).insert('\n', lineFormats);
  258. this.quill.updateContents(delta, Quill.sources.USER);
  259. this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
  260. this.quill.focus();
  261. }
  262. }
  263. const defaultOptions = {
  264. bindings: {
  265. bold: makeFormatHandler('bold'),
  266. italic: makeFormatHandler('italic'),
  267. underline: makeFormatHandler('underline'),
  268. indent: {
  269. // highlight tab or tab at beginning of list, indent or blockquote
  270. key: 'Tab',
  271. format: ['blockquote', 'indent', 'list'],
  272. handler(range, context) {
  273. if (context.collapsed && context.offset !== 0) return true;
  274. this.quill.format('indent', '+1', Quill.sources.USER);
  275. return false;
  276. }
  277. },
  278. outdent: {
  279. key: 'Tab',
  280. shiftKey: true,
  281. format: ['blockquote', 'indent', 'list'],
  282. // highlight tab or tab at beginning of list, indent or blockquote
  283. handler(range, context) {
  284. if (context.collapsed && context.offset !== 0) return true;
  285. this.quill.format('indent', '-1', Quill.sources.USER);
  286. return false;
  287. }
  288. },
  289. 'outdent backspace': {
  290. key: 'Backspace',
  291. collapsed: true,
  292. shiftKey: null,
  293. metaKey: null,
  294. ctrlKey: null,
  295. altKey: null,
  296. format: ['indent', 'list'],
  297. offset: 0,
  298. handler(range, context) {
  299. if (context.format.indent != null) {
  300. this.quill.format('indent', '-1', Quill.sources.USER);
  301. } else if (context.format.list != null) {
  302. this.quill.format('list', false, Quill.sources.USER);
  303. }
  304. }
  305. },
  306. 'indent code-block': makeCodeBlockHandler(true),
  307. 'outdent code-block': makeCodeBlockHandler(false),
  308. 'remove tab': {
  309. key: 'Tab',
  310. shiftKey: true,
  311. collapsed: true,
  312. prefix: /\t$/,
  313. handler(range) {
  314. this.quill.deleteText(range.index - 1, 1, Quill.sources.USER);
  315. }
  316. },
  317. tab: {
  318. key: 'Tab',
  319. handler(range, context) {
  320. if (context.format.table) return true;
  321. this.quill.history.cutoff();
  322. const delta = new Delta().retain(range.index).delete(range.length).insert('\t');
  323. this.quill.updateContents(delta, Quill.sources.USER);
  324. this.quill.history.cutoff();
  325. this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
  326. return false;
  327. }
  328. },
  329. 'blockquote empty enter': {
  330. key: 'Enter',
  331. collapsed: true,
  332. format: ['blockquote'],
  333. empty: true,
  334. handler() {
  335. this.quill.format('blockquote', false, Quill.sources.USER);
  336. }
  337. },
  338. 'list empty enter': {
  339. key: 'Enter',
  340. collapsed: true,
  341. format: ['list'],
  342. empty: true,
  343. handler(range, context) {
  344. const formats = {
  345. list: false
  346. };
  347. if (context.format.indent) {
  348. formats.indent = false;
  349. }
  350. this.quill.formatLine(range.index, range.length, formats, Quill.sources.USER);
  351. }
  352. },
  353. 'checklist enter': {
  354. key: 'Enter',
  355. collapsed: true,
  356. format: {
  357. list: 'checked'
  358. },
  359. handler(range) {
  360. const [line, offset] = this.quill.getLine(range.index);
  361. const formats = {
  362. // @ts-expect-error Fix me later
  363. ...line.formats(),
  364. list: 'checked'
  365. };
  366. const delta = new Delta().retain(range.index).insert('\n', formats)
  367. // @ts-expect-error Fix me later
  368. .retain(line.length() - offset - 1).retain(1, {
  369. list: 'unchecked'
  370. });
  371. this.quill.updateContents(delta, Quill.sources.USER);
  372. this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
  373. this.quill.scrollSelectionIntoView();
  374. }
  375. },
  376. 'header enter': {
  377. key: 'Enter',
  378. collapsed: true,
  379. format: ['header'],
  380. suffix: /^$/,
  381. handler(range, context) {
  382. const [line, offset] = this.quill.getLine(range.index);
  383. const delta = new Delta().retain(range.index).insert('\n', context.format)
  384. // @ts-expect-error Fix me later
  385. .retain(line.length() - offset - 1).retain(1, {
  386. header: null
  387. });
  388. this.quill.updateContents(delta, Quill.sources.USER);
  389. this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
  390. this.quill.scrollSelectionIntoView();
  391. }
  392. },
  393. 'table backspace': {
  394. key: 'Backspace',
  395. format: ['table'],
  396. collapsed: true,
  397. offset: 0,
  398. handler() {}
  399. },
  400. 'table delete': {
  401. key: 'Delete',
  402. format: ['table'],
  403. collapsed: true,
  404. suffix: /^$/,
  405. handler() {}
  406. },
  407. 'table enter': {
  408. key: 'Enter',
  409. shiftKey: null,
  410. format: ['table'],
  411. handler(range) {
  412. const module = this.quill.getModule('table');
  413. if (module) {
  414. // @ts-expect-error
  415. const [table, row, cell, offset] = module.getTable(range);
  416. const shift = tableSide(table, row, cell, offset);
  417. if (shift == null) return;
  418. let index = table.offset();
  419. if (shift < 0) {
  420. const delta = new Delta().retain(index).insert('\n');
  421. this.quill.updateContents(delta, Quill.sources.USER);
  422. this.quill.setSelection(range.index + 1, range.length, Quill.sources.SILENT);
  423. } else if (shift > 0) {
  424. index += table.length();
  425. const delta = new Delta().retain(index).insert('\n');
  426. this.quill.updateContents(delta, Quill.sources.USER);
  427. this.quill.setSelection(index, Quill.sources.USER);
  428. }
  429. }
  430. }
  431. },
  432. 'table tab': {
  433. key: 'Tab',
  434. shiftKey: null,
  435. format: ['table'],
  436. handler(range, context) {
  437. const {
  438. event,
  439. line: cell
  440. } = context;
  441. const offset = cell.offset(this.quill.scroll);
  442. if (event.shiftKey) {
  443. this.quill.setSelection(offset - 1, Quill.sources.USER);
  444. } else {
  445. this.quill.setSelection(offset + cell.length(), Quill.sources.USER);
  446. }
  447. }
  448. },
  449. 'list autofill': {
  450. key: ' ',
  451. shiftKey: null,
  452. collapsed: true,
  453. format: {
  454. 'code-block': false,
  455. blockquote: false,
  456. table: false
  457. },
  458. prefix: /^\s*?(\d+\.|-|\*|\[ ?\]|\[x\])$/,
  459. handler(range, context) {
  460. if (this.quill.scroll.query('list') == null) return true;
  461. const {
  462. length
  463. } = context.prefix;
  464. const [line, offset] = this.quill.getLine(range.index);
  465. if (offset > length) return true;
  466. let value;
  467. switch (context.prefix.trim()) {
  468. case '[]':
  469. case '[ ]':
  470. value = 'unchecked';
  471. break;
  472. case '[x]':
  473. value = 'checked';
  474. break;
  475. case '-':
  476. case '*':
  477. value = 'bullet';
  478. break;
  479. default:
  480. value = 'ordered';
  481. }
  482. this.quill.insertText(range.index, ' ', Quill.sources.USER);
  483. this.quill.history.cutoff();
  484. const delta = new Delta().retain(range.index - offset).delete(length + 1)
  485. // @ts-expect-error Fix me later
  486. .retain(line.length() - 2 - offset).retain(1, {
  487. list: value
  488. });
  489. this.quill.updateContents(delta, Quill.sources.USER);
  490. this.quill.history.cutoff();
  491. this.quill.setSelection(range.index - length, Quill.sources.SILENT);
  492. return false;
  493. }
  494. },
  495. 'code exit': {
  496. key: 'Enter',
  497. collapsed: true,
  498. format: ['code-block'],
  499. prefix: /^$/,
  500. suffix: /^\s*$/,
  501. handler(range) {
  502. const [line, offset] = this.quill.getLine(range.index);
  503. let numLines = 2;
  504. let cur = line;
  505. while (cur != null && cur.length() <= 1 && cur.formats()['code-block']) {
  506. // @ts-expect-error
  507. cur = cur.prev;
  508. numLines -= 1;
  509. // Requisite prev lines are empty
  510. if (numLines <= 0) {
  511. const delta = new Delta()
  512. // @ts-expect-error Fix me later
  513. .retain(range.index + line.length() - offset - 2).retain(1, {
  514. 'code-block': null
  515. }).delete(1);
  516. this.quill.updateContents(delta, Quill.sources.USER);
  517. this.quill.setSelection(range.index - 1, Quill.sources.SILENT);
  518. return false;
  519. }
  520. }
  521. return true;
  522. }
  523. },
  524. 'embed left': makeEmbedArrowHandler('ArrowLeft', false),
  525. 'embed left shift': makeEmbedArrowHandler('ArrowLeft', true),
  526. 'embed right': makeEmbedArrowHandler('ArrowRight', false),
  527. 'embed right shift': makeEmbedArrowHandler('ArrowRight', true),
  528. 'table down': makeTableArrowHandler(false),
  529. 'table up': makeTableArrowHandler(true)
  530. }
  531. };
  532. Keyboard.DEFAULTS = defaultOptions;
  533. function makeCodeBlockHandler(indent) {
  534. return {
  535. key: 'Tab',
  536. shiftKey: !indent,
  537. format: {
  538. 'code-block': true
  539. },
  540. handler(range, _ref) {
  541. let {
  542. event
  543. } = _ref;
  544. const CodeBlock = this.quill.scroll.query('code-block');
  545. // @ts-expect-error
  546. const {
  547. TAB
  548. } = CodeBlock;
  549. if (range.length === 0 && !event.shiftKey) {
  550. this.quill.insertText(range.index, TAB, Quill.sources.USER);
  551. this.quill.setSelection(range.index + TAB.length, Quill.sources.SILENT);
  552. return;
  553. }
  554. const lines = range.length === 0 ? this.quill.getLines(range.index, 1) : this.quill.getLines(range);
  555. let {
  556. index,
  557. length
  558. } = range;
  559. lines.forEach((line, i) => {
  560. if (indent) {
  561. line.insertAt(0, TAB);
  562. if (i === 0) {
  563. index += TAB.length;
  564. } else {
  565. length += TAB.length;
  566. }
  567. // @ts-expect-error Fix me later
  568. } else if (line.domNode.textContent.startsWith(TAB)) {
  569. line.deleteAt(0, TAB.length);
  570. if (i === 0) {
  571. index -= TAB.length;
  572. } else {
  573. length -= TAB.length;
  574. }
  575. }
  576. });
  577. this.quill.update(Quill.sources.USER);
  578. this.quill.setSelection(index, length, Quill.sources.SILENT);
  579. }
  580. };
  581. }
  582. function makeEmbedArrowHandler(key, shiftKey) {
  583. const where = key === 'ArrowLeft' ? 'prefix' : 'suffix';
  584. return {
  585. key,
  586. shiftKey,
  587. altKey: null,
  588. [where]: /^$/,
  589. handler(range) {
  590. let {
  591. index
  592. } = range;
  593. if (key === 'ArrowRight') {
  594. index += range.length + 1;
  595. }
  596. const [leaf] = this.quill.getLeaf(index);
  597. if (!(leaf instanceof EmbedBlot)) return true;
  598. if (key === 'ArrowLeft') {
  599. if (shiftKey) {
  600. this.quill.setSelection(range.index - 1, range.length + 1, Quill.sources.USER);
  601. } else {
  602. this.quill.setSelection(range.index - 1, Quill.sources.USER);
  603. }
  604. } else if (shiftKey) {
  605. this.quill.setSelection(range.index, range.length + 1, Quill.sources.USER);
  606. } else {
  607. this.quill.setSelection(range.index + range.length + 1, Quill.sources.USER);
  608. }
  609. return false;
  610. }
  611. };
  612. }
  613. function makeFormatHandler(format) {
  614. return {
  615. key: format[0],
  616. shortKey: true,
  617. handler(range, context) {
  618. this.quill.format(format, !context.format[format], Quill.sources.USER);
  619. }
  620. };
  621. }
  622. function makeTableArrowHandler(up) {
  623. return {
  624. key: up ? 'ArrowUp' : 'ArrowDown',
  625. collapsed: true,
  626. format: ['table'],
  627. handler(range, context) {
  628. // TODO move to table module
  629. const key = up ? 'prev' : 'next';
  630. const cell = context.line;
  631. const targetRow = cell.parent[key];
  632. if (targetRow != null) {
  633. if (targetRow.statics.blotName === 'table-row') {
  634. // @ts-expect-error
  635. let targetCell = targetRow.children.head;
  636. let cur = cell;
  637. while (cur.prev != null) {
  638. // @ts-expect-error
  639. cur = cur.prev;
  640. targetCell = targetCell.next;
  641. }
  642. const index = targetCell.offset(this.quill.scroll) + Math.min(context.offset, targetCell.length() - 1);
  643. this.quill.setSelection(index, 0, Quill.sources.USER);
  644. }
  645. } else {
  646. // @ts-expect-error
  647. const targetLine = cell.table()[key];
  648. if (targetLine != null) {
  649. if (up) {
  650. this.quill.setSelection(targetLine.offset(this.quill.scroll) + targetLine.length() - 1, 0, Quill.sources.USER);
  651. } else {
  652. this.quill.setSelection(targetLine.offset(this.quill.scroll), 0, Quill.sources.USER);
  653. }
  654. }
  655. }
  656. return false;
  657. }
  658. };
  659. }
  660. function normalize(binding) {
  661. if (typeof binding === 'string' || typeof binding === 'number') {
  662. binding = {
  663. key: binding
  664. };
  665. } else if (typeof binding === 'object') {
  666. binding = cloneDeep(binding);
  667. } else {
  668. return null;
  669. }
  670. if (binding.shortKey) {
  671. binding[SHORTKEY] = binding.shortKey;
  672. delete binding.shortKey;
  673. }
  674. return binding;
  675. }
  676. // TODO: Move into quill.ts or editor.ts
  677. function deleteRange(_ref2) {
  678. let {
  679. quill,
  680. range
  681. } = _ref2;
  682. const lines = quill.getLines(range);
  683. let formats = {};
  684. if (lines.length > 1) {
  685. const firstFormats = lines[0].formats();
  686. const lastFormats = lines[lines.length - 1].formats();
  687. formats = AttributeMap.diff(lastFormats, firstFormats) || {};
  688. }
  689. quill.deleteText(range, Quill.sources.USER);
  690. if (Object.keys(formats).length > 0) {
  691. quill.formatLine(range.index, 1, formats, Quill.sources.USER);
  692. }
  693. quill.setSelection(range.index, Quill.sources.SILENT);
  694. }
  695. function tableSide(_table, row, cell, offset) {
  696. if (row.prev == null && row.next == null) {
  697. if (cell.prev == null && cell.next == null) {
  698. return offset === 0 ? -1 : 1;
  699. }
  700. return cell.prev == null ? -1 : 1;
  701. }
  702. if (row.prev == null) {
  703. return -1;
  704. }
  705. if (row.next == null) {
  706. return 1;
  707. }
  708. return null;
  709. }
  710. export { Keyboard as default, SHORTKEY, normalize, deleteRange };
  711. //# sourceMappingURL=keyboard.js.map