colorpicker-helper.js 5.3 KB

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