colorpicker-helper.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /* global CodeMirror loadScript editors showHelp */
  2. 'use strict';
  3. // eslint-disable-next-line no-var
  4. var initColorpicker = () => {
  5. initOverlayHooks();
  6. onDOMready().then(() => {
  7. $('#colorpicker-settings').onclick = configureColorpicker;
  8. });
  9. const scripts = [
  10. '/vendor-overwrites/colorpicker/colorpicker.css',
  11. '/vendor-overwrites/colorpicker/colorpicker.js',
  12. '/vendor-overwrites/colorpicker/colorview.js',
  13. ];
  14. prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey);
  15. prefs.subscribe(['editor.colorpicker'], colorpickerOnDemand);
  16. return prefs.get('editor.colorpicker') && colorpickerOnDemand(null, true);
  17. function colorpickerOnDemand(id, enabled) {
  18. return loadScript(enabled && scripts)
  19. .then(() => setColorpickerOption(id, enabled));
  20. }
  21. function setColorpickerOption(id, enabled) {
  22. const defaults = CodeMirror.defaults;
  23. const keyName = prefs.get('editor.colorpicker.hotkey');
  24. delete defaults.extraKeys[keyName];
  25. defaults.colorpicker = enabled;
  26. if (enabled) {
  27. if (keyName) {
  28. CodeMirror.commands.colorpicker = invokeColorpicker;
  29. defaults.extraKeys[keyName] = 'colorpicker';
  30. }
  31. defaults.colorpicker = {
  32. forceUpdate: editors.length > 0,
  33. tooltip: t('colorpickerTooltip'),
  34. popupOptions: {
  35. tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'),
  36. hexUppercase: prefs.get('editor.colorpicker.hexUppercase'),
  37. hideDelay: 5000,
  38. embedderCallback: state => {
  39. ['hexUppercase', 'color']
  40. .filter(name => state[name] !== prefs.get('editor.colorpicker.' + name))
  41. .forEach(name => prefs.set('editor.colorpicker.' + name, state[name]));
  42. },
  43. },
  44. };
  45. }
  46. // on page load runs before CodeMirror.setOption is defined
  47. editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker));
  48. }
  49. function registerHotkey(id, hotkey) {
  50. CodeMirror.commands.colorpicker = invokeColorpicker;
  51. const extraKeys = CodeMirror.defaults.extraKeys;
  52. for (const key in extraKeys) {
  53. if (extraKeys[key] === 'colorpicker') {
  54. delete extraKeys[key];
  55. break;
  56. }
  57. }
  58. if (hotkey) {
  59. extraKeys[hotkey] = 'colorpicker';
  60. }
  61. }
  62. function invokeColorpicker(cm) {
  63. cm.state.colorpicker.openPopup(prefs.get('editor.colorpicker.color'));
  64. }
  65. function configureColorpicker() {
  66. const input = $element({
  67. tag: 'input',
  68. type: 'search',
  69. spellcheck: false,
  70. value: prefs.get('editor.colorpicker.hotkey'),
  71. onkeydown(event) {
  72. const key = CodeMirror.keyName(event);
  73. // ignore: [Shift?] characters, modifiers-only, [Shift?] Esc, Enter, [Shift?] Tab
  74. if (key === 'Enter' || key === 'Esc') {
  75. $('#help-popup .dismiss').onclick();
  76. return;
  77. } else if (/^(Space|(Shift-)?(Esc|Tab|[!-~])|(Shift-?|Ctrl-?|Alt-?|Cmd-?)*)$/.test(key)) {
  78. this.setCustomValidity('Not allowed');
  79. } else {
  80. this.setCustomValidity('');
  81. prefs.set('editor.colorpicker.hotkey', key);
  82. }
  83. event.preventDefault();
  84. event.stopPropagation();
  85. this.value = key;
  86. },
  87. oninput() {
  88. // fired on pressing "x" to clear the field
  89. prefs.set('editor.colorpicker.hotkey', '');
  90. },
  91. onpaste(event) {
  92. event.preventDefault();
  93. }
  94. });
  95. const popup = showHelp(t('helpKeyMapHotkey'), input);
  96. if (this instanceof Element) {
  97. const bounds = this.getBoundingClientRect();
  98. popup.style.left = bounds.right + 10 + 'px';
  99. popup.style.top = bounds.top - popup.clientHeight / 2 + 'px';
  100. popup.style.right = 'auto';
  101. }
  102. input.focus();
  103. }
  104. function initOverlayHooks() {
  105. const COLORVIEW_DISABLED_SUFFIX = ' colorview-disabled';
  106. const COLORVIEW_NEXT_DISABLED_SUFFIX = ' colorview-next-disabled';
  107. const originalAddOverlay = CodeMirror.prototype.addOverlay;
  108. CodeMirror.prototype.addOverlay = addOverlayHook;
  109. function addOverlayHook(overlay) {
  110. if (overlay.token !== tokenHook && (
  111. overlay === (this.state.matchHighlighter || {}).overlay ||
  112. overlay === (this.state.search || {}).overlay)) {
  113. overlay.colopickerHelper = {token: overlay.token};
  114. overlay.token = tokenHook;
  115. }
  116. originalAddOverlay.apply(this, arguments);
  117. }
  118. function tokenHook(stream) {
  119. const style = this.colopickerHelper.token.apply(this, arguments);
  120. if (!style) {
  121. return style;
  122. }
  123. const {start, pos, lineOracle: {baseTokens}} = stream;
  124. if (!baseTokens) {
  125. return style;
  126. }
  127. for (let prev = 0, i = 1; i < baseTokens.length; i += 2) {
  128. const end = baseTokens[i];
  129. if (prev <= start && start <= end) {
  130. const base = baseTokens[i + 1];
  131. if (base && base.includes('colorview')) {
  132. return style +
  133. (start > prev ? COLORVIEW_DISABLED_SUFFIX : '') +
  134. (pos < end ? COLORVIEW_NEXT_DISABLED_SUFFIX : '');
  135. }
  136. } else if (end > pos) {
  137. break;
  138. }
  139. prev = end;
  140. }
  141. return style;
  142. }
  143. }
  144. };