bubble.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import merge from 'lodash.merge';
  2. import Emitter from '../core/emitter';
  3. import BaseTheme, { BaseTooltip } from './base';
  4. import { Range } from '../core/selection';
  5. import icons from '../ui/icons';
  6. const TOOLBAR_CONFIG = [
  7. ['bold', 'italic', 'link'],
  8. [{ header: 1 }, { header: 2 }, 'blockquote'],
  9. ];
  10. class BubbleTooltip extends BaseTooltip {
  11. constructor(quill, bounds) {
  12. super(quill, bounds);
  13. this.quill.on(
  14. Emitter.events.EDITOR_CHANGE,
  15. (type, range, oldRange, source) => {
  16. if (type !== Emitter.events.SELECTION_CHANGE) return;
  17. if (
  18. range != null &&
  19. range.length > 0 &&
  20. source === Emitter.sources.USER
  21. ) {
  22. this.show();
  23. // Lock our width so we will expand beyond our offsetParent boundaries
  24. this.root.style.left = '0px';
  25. this.root.style.width = '';
  26. this.root.style.width = `${this.root.offsetWidth}px`;
  27. const lines = this.quill.getLines(range.index, range.length);
  28. if (lines.length === 1) {
  29. this.position(this.quill.getBounds(range));
  30. } else {
  31. const lastLine = lines[lines.length - 1];
  32. const index = this.quill.getIndex(lastLine);
  33. const length = Math.min(
  34. lastLine.length() - 1,
  35. range.index + range.length - index,
  36. );
  37. const indexBounds = this.quill.getBounds(new Range(index, length));
  38. this.position(indexBounds);
  39. }
  40. } else if (
  41. document.activeElement !== this.textbox &&
  42. this.quill.hasFocus()
  43. ) {
  44. this.hide();
  45. }
  46. },
  47. );
  48. }
  49. listen() {
  50. super.listen();
  51. this.root.querySelector('.ql-close').addEventListener('click', () => {
  52. this.root.classList.remove('ql-editing');
  53. });
  54. this.quill.on(Emitter.events.SCROLL_OPTIMIZE, () => {
  55. // Let selection be restored by toolbar handlers before repositioning
  56. setTimeout(() => {
  57. if (this.root.classList.contains('ql-hidden')) return;
  58. const range = this.quill.getSelection();
  59. if (range != null) {
  60. this.position(this.quill.getBounds(range));
  61. }
  62. }, 1);
  63. });
  64. }
  65. cancel() {
  66. this.show();
  67. }
  68. position(reference) {
  69. const shift = super.position(reference);
  70. const arrow = this.root.querySelector('.ql-tooltip-arrow');
  71. arrow.style.marginLeft = '';
  72. if (shift !== 0) {
  73. arrow.style.marginLeft = `${-1 * shift - arrow.offsetWidth / 2}px`;
  74. }
  75. return shift;
  76. }
  77. }
  78. BubbleTooltip.TEMPLATE = [
  79. '<span class="ql-tooltip-arrow"></span>',
  80. '<div class="ql-tooltip-editor">',
  81. '<input type="text" data-formula="e=mc^2" data-link="https://quilljs.com" data-video="Embed URL">',
  82. '<a class="ql-close"></a>',
  83. '</div>',
  84. ].join('');
  85. class BubbleTheme extends BaseTheme {
  86. constructor(quill, options) {
  87. if (
  88. options.modules.toolbar != null &&
  89. options.modules.toolbar.container == null
  90. ) {
  91. options.modules.toolbar.container = TOOLBAR_CONFIG;
  92. }
  93. super(quill, options);
  94. this.quill.container.classList.add('ql-bubble');
  95. }
  96. extendToolbar(toolbar) {
  97. this.tooltip = new BubbleTooltip(this.quill, this.options.bounds);
  98. this.tooltip.root.appendChild(toolbar.container);
  99. this.buildButtons(toolbar.container.querySelectorAll('button'), icons);
  100. this.buildPickers(toolbar.container.querySelectorAll('select'), icons);
  101. }
  102. }
  103. BubbleTheme.DEFAULTS = merge({}, BaseTheme.DEFAULTS, {
  104. modules: {
  105. toolbar: {
  106. handlers: {
  107. link(value) {
  108. if (!value) {
  109. this.quill.format('link', false);
  110. } else {
  111. this.quill.theme.tooltip.edit();
  112. }
  113. },
  114. },
  115. },
  116. },
  117. });
  118. export { BubbleTooltip, BubbleTheme as default };