bubble.js 3.5 KB

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