1/*  DokuWiki MoaiEditor Button.js file
2    Version : 0.5a (May 6, 2026)
3    Author  : MoaiTools <info@moaitools.org>
4    License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */
5
6/*  Class to create the buttons you see at the top.
7    It can handle buttons with multiple states.
8*/
9/*
10    DEF
11
12    outer:
13    name: for #id and local storage variable names
14    type: submit
15    icon:
16    onClick:
17    tooltip:
18    tooltip_width:
19    default_mode:
20    classes:
21    cls_remove:
22    states[mode]
23        icon:
24        onClick:
25        tooltip:
26        next_mode:
27        classes_add:
28        classes_remove:
29*/
30MoaiEditor.Button = class {
31
32    constructor(def) {
33
34        // Parameters
35        this.id      = 'moaied__btn_'+def.name;
36        this.name    = def.name;
37        this.outer   = def.outer;
38        this.type    = def?.type ?? '';
39        this.icon    = def?.icon;
40        this.text    = def?.text;
41        this.onClick = def.onClick;
42        this.classes = def?.classes;
43        this.states  = def?.states;
44        this.tooltip = def?.tooltip;
45        this.tooltip_width = def?.tooltip_width;
46        this.tooltips = def?.tooltips;
47        this.cls_remove = def?.cls_remove;
48        this.default_mode = def?.default_mode;
49
50        // Objects
51        if (this.states !== undefined) {
52            const mode_names = Object.keys(this.states);
53            this.storage = new MoaiEditor.LocalStorage('btn_'+def.name, this.default_mode, mode_names);
54        }
55        // Init
56        this.__init();
57    }
58    // ────────────────────────────────────
59    __init() {
60        const id      = 'id="'+this.id+'"';
61        const icon    = '<i></i>';
62        const text    = '<span class="moaied-button-text"></span>';
63        const type    = 'type="'+this.type+'"';
64        const classes = 'class="moaied-button moaied-tooltip '+(this.classes ?? '')+'"';
65        const tooltip = '<span class="moaied-tooltip-text" '+this.__getTooltipWidthStyle()+'></span>';
66        const onclick = 'onclick="moaiEditor.buttons.'+this.name+'.__onClick(event)"';
67        const html = '<button form="" '+id+' '+type+' '+classes+' '+onclick+'>'+text+icon+tooltip+'</button>';
68        this.handle = moaiEditor.createHTML(html);
69        //document.body.appendChild(this.handle);
70        this.update();
71    }
72    // ────────────────────────────────────
73    __getTooltipWidthStyle() {
74        if (!this.tooltip_width)
75            return '';
76        const width = "width:"+this.tooltip_width+"px !important;";
77        const mleft = "margin-left: -"+Math.round(this.tooltip_width/2)+"px !important;";
78        return 'style="'+width+mleft+'"';
79    }
80    // ────────────────────────────────────
81    __onClick(event) {
82
83        // Toggle state and call state function
84        if (this.mode !== null  &&  this.states) {
85
86            // Change the mode if applicable
87            const next_mode = this.states[this.mode]?.next_mode;
88            if (next_mode)
89                this.mode = next_mode;
90
91            // Get the current state
92            const state = this.states[this.mode];
93
94            // Call a state function if defined
95            if (state.onClick)
96                state.onClick(event);
97        }
98        // Call a common function if defined
99        if (this.onClick)
100            this.onClick(event);
101
102        // Update the button appearance
103        this.update();
104    }
105    // ────────────────────────────────────
106    update() {
107        // Return if the browser local storage failed
108        if (this.state === null)
109            return;
110        // Get the handle and the state
111        var state = {};
112        if (this.states)
113            state = this.states[this.mode];
114        // Icon
115        var icon = '';
116        if (state?.icon ?? this.icon) {
117            var icon = state?.icon ?? this.icon;
118            if (!icon.startsWith('<img'))
119                 icon = moaiEditor.icons?.[state?.icon ?? this.icon];
120        }
121        this.handle.querySelector(':scope > i').innerHTML = icon;
122        // Text
123        var text = '';
124        if (state?.text ?? this.text)
125            text = state?.text ?? this.text;
126        this.handle.querySelector(':scope > span.moaied-button-text').innerHTML = text;
127        // State tooltip
128        var tooltip = this.handle.querySelector(':scope > span.moaied-tooltip-text');
129        if (state?.tooltip ?? this?.tooltip)
130            tooltip.style.display = 'block';
131        // Button tooltip
132        else
133            tooltip.style.display = 'none';
134        tooltip.innerHTML = state?.tooltip ?? this?.tooltip;
135        // Composite tooltip (tooltip is composed of messages added by several external processes)
136        if (this.tooltips) {
137            let html = '';
138            for (let key in this.tooltips)
139                html += this.tooltips[key];
140            tooltip.innerHTML = html;
141        }
142        // Classes
143        var classList = new MoaiEditor.ElementClassList(this.handle);
144        classList.removeByString(this?.cls_remove);
145        classList.removeByString(state?.cls_remove);
146        classList.addByString(this?.classes);
147        classList.addByString(state?.cls_add);
148        // Mirror mode into data-attribute for convinience
149        this.handle.dataset.moaiedMode = this.mode;
150    }
151    // ────────────────────────────────────
152    set mode(mode) {
153        if (this.storage !== undefined) {
154            this.storage.value = mode;
155            this.update();
156        }
157    }
158    // ────────────────────────────────────
159    get mode() {
160        if (this.storage !== undefined) {
161            return this.storage.value;
162        } else
163            return null;
164    }
165}; // End Class
166
167// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆
168
169MoaiEditor.ElementClassList = class {
170
171    constructor(node) {
172        this.node = node;
173    }
174    // ────────────────────────────────────
175    addByString(string) {
176        for (let name of this.splitString(string))
177            this.node.classList.add(name)
178    }
179    // ────────────────────────────────────
180    removeByString(string) {
181        for (let name of this.splitString(string))
182            this.node.classList.remove(name)
183    }
184    // ────────────────────────────────────
185    splitString(string) {
186        if (!string)
187            return [];
188        var array = [];
189        for (let name of string.split(" ")) {
190            name = name.trim();
191            if (name.length > 0)
192                array.push(name);
193        }
194        return array;
195    }
196}; // End Class
197
198