]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/ui/framework/blocks/color-picker.ts
c742ddc7723f76556b013815eb5f80476f562bba
[bookstack] / resources / js / wysiwyg / ui / framework / blocks / color-picker.ts
1 import {EditorUiContext, EditorUiElement} from "../core";
2 import {el} from "../../../utils/dom";
3
4 import removeIcon from "@icons/editor/color-clear.svg";
5 import selectIcon from "@icons/editor/color-select.svg";
6 import {uniqueIdSmall} from "../../../../services/util";
7
8 const colorChoices = [
9     '#000000',
10     '#ffffff',
11
12     '#BFEDD2',
13     '#FBEEB8',
14     '#F8CAC6',
15     '#ECCAFA',
16     '#C2E0F4',
17
18     '#2DC26B',
19     '#F1C40F',
20     '#E03E2D',
21     '#B96AD9',
22     '#3598DB',
23
24     '#169179',
25     '#E67E23',
26     '#BA372A',
27     '#843FA1',
28     '#236FA1',
29
30     '#ECF0F1',
31     '#CED4D9',
32     '#95A5A6',
33     '#7E8C8D',
34     '#34495E',
35 ];
36
37 const storageKey = 'bs-lexical-custom-colors';
38
39 export type EditorColorPickerCallback = (color: string, context: EditorUiContext) => void;
40
41 export class EditorColorPicker extends EditorUiElement {
42
43     protected callback: EditorColorPickerCallback;
44
45     constructor(callback: EditorColorPickerCallback) {
46         super();
47         this.callback = callback;
48     }
49
50     buildDOM(): HTMLElement {
51         const id = uniqueIdSmall();
52
53         const allChoices = [...colorChoices, ...this.getCustomColorChoices()];
54         const colorOptions = allChoices.map(choice => {
55             return el('div', {
56                 class: 'editor-color-select-option',
57                 style: `background-color: ${choice}`,
58                 'data-color': choice,
59                 'aria-label': choice,
60             });
61         });
62
63         const removeButton = el('div', {
64             class: 'editor-color-select-option',
65             'data-color': '',
66             title: 'Clear color',
67         }, []);
68         removeButton.innerHTML = removeIcon;
69         colorOptions.push(removeButton);
70
71         const selectButton = el('label', {
72             class: 'editor-color-select-option',
73             for: `color-select-${id}`,
74             'data-color': '',
75             title: 'Custom color',
76         }, []);
77         selectButton.innerHTML = selectIcon;
78         colorOptions.push(selectButton);
79
80         const input = el('input', {type: 'color', hidden: 'true', id: `color-select-${id}`}) as HTMLInputElement;
81         colorOptions.push(input);
82         input.addEventListener('change', e => {
83             if (input.value) {
84                 this.storeCustomColorChoice(input.value);
85                 this.setColor(input.value);
86                 this.rebuildDOM();
87             }
88         });
89
90         const colorRows = [];
91         for (let i = 0; i < colorOptions.length; i+=5) {
92             const options = colorOptions.slice(i, i + 5);
93             colorRows.push(el('div', {
94                 class: 'editor-color-select-row',
95             }, options));
96         }
97
98         const wrapper = el('div', {
99             class: 'editor-color-select',
100         }, colorRows);
101
102         wrapper.addEventListener('click', this.onClick.bind(this));
103
104         return wrapper;
105     }
106
107     storeCustomColorChoice(color: string) {
108         if (colorChoices.includes(color)) {
109             return;
110         }
111
112         const customColors: string[] = this.getCustomColorChoices();
113         if (customColors.includes(color)) {
114             return;
115         }
116
117         customColors.push(color);
118         window.localStorage.setItem(storageKey, JSON.stringify(customColors));
119     }
120
121     getCustomColorChoices(): string[] {
122         return JSON.parse(window.localStorage.getItem(storageKey) || '[]');
123     }
124
125     onClick(event: MouseEvent) {
126         const colorEl = (event.target as HTMLElement).closest('[data-color]') as HTMLElement;
127         if (!colorEl) return;
128
129         const color = colorEl.dataset.color as string;
130         this.setColor(color);
131     }
132
133     setColor(color: string) {
134         this.callback(color, this.getContext());
135     }
136 }