xref: /plugin/newpagefill/script.js (revision 9a36670832b754c53fa81850c7b00d5bb57a3705)
1(function () {
2    function escapeRegExp(text) {
3        return String(text).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
4    }
5
6    function slugifyTitle(title, options) {
7        const sep = options && options.sepchar ? options.sepchar : '_';
8        const sepPattern = new RegExp(escapeRegExp(sep) + '+', 'g');
9        const trimPattern = new RegExp('^' + escapeRegExp(sep) + '|' + escapeRegExp(sep) + '$', 'g');
10
11        return String(title || '')
12            .normalize('NFD')
13            .replace(/[\u0300-\u036f]/g, '')
14            .replace(/['’]/g, '')
15            .toLowerCase()
16            .replace(/[^a-z0-9]+/g, sep)
17            .replace(sepPattern, sep)
18            .replace(trimPattern, '');
19    }
20
21    function getStrings() {
22        const lang = window.LANG && LANG.plugins && LANG.plugins.newpagefill ? LANG.plugins.newpagefill : {};
23        return {
24            title: lang.dialog_title || 'Creer une nouvelle page',
25            pageTitle: lang.dialog_page_title || 'Titre',
26            pageId: lang.dialog_page_id || 'Identifiant',
27            namespace: lang.dialog_namespace || 'Namespace',
28            pageMode: lang.dialog_page_mode || 'Type de creation',
29            pageModeStart: lang.dialog_page_mode_start || 'Page de demarrage',
30            pageModeNone: lang.dialog_page_mode_none || 'Page directe',
31            pageModeSame: lang.dialog_page_mode_same || 'Sous-page du meme nom',
32            create: lang.dialog_create || 'Creer',
33            cancel: lang.dialog_cancel || 'Annuler',
34            required: lang.dialog_required || 'L identifiant est requis.'
35        };
36    }
37
38    function getPluginConfig() {
39        return window.JSINFO && JSINFO.plugins && JSINFO.plugins.newpagefill
40            ? JSINFO.plugins.newpagefill
41            : {};
42    }
43
44    function appendToUrl(base, params) {
45        return base + (base.indexOf('?') >= 0 ? '&' : '?') + params;
46    }
47
48    function buildBaseHref(namespace, options) {
49        const root = typeof DOKU_BASE !== 'undefined' && DOKU_BASE ? DOKU_BASE : '/';
50        const cleaned = String(namespace || '').trim().replace(/^:+|:+$/g, '');
51
52        if (options && Number(options.userewrite) === 1) {
53            if (!cleaned) return root.replace(/\/$/, '');
54            return root.replace(/\/$/, '') + '/' + cleaned.split(':').map(encodeURIComponent).join('/');
55        }
56
57        const base = root.replace(/\/$/, '/');
58        if (!cleaned) return base;
59        return appendToUrl(base, 'id=' + encodeURIComponent(cleaned));
60    }
61
62    function buildTargetPageId(namespace, pageId, start) {
63        const parts = [];
64        const cleanedNamespace = String(namespace || '').trim().replace(/^:+|:+$/g, '');
65        const cleanedPageId = String(pageId || '').trim().replace(/^:+|:+$/g, '');
66        const cleanedStart = String(start || '').trim().replace(/^:+|:+$/g, '');
67
68        if (cleanedNamespace) parts.push(cleanedNamespace);
69        if (cleanedPageId) parts.push(cleanedPageId);
70        if (cleanedStart) parts.push(cleanedStart);
71
72        return parts.join(':');
73    }
74
75    function resolveStartSegment(pageId, startOption, defaultStart, defaultMode) {
76        if (startOption === null || typeof startOption === 'undefined') {
77            startOption = defaultMode;
78        }
79
80        if (String(startOption).toLowerCase() === '@ask@' || String(startOption).toLowerCase() === 'ask') {
81            return null;
82        }
83
84        if (startOption === true) {
85            return String(defaultStart || '').trim();
86        }
87
88        if (startOption === false || startOption === '' || String(startOption).toLowerCase() === 'none') {
89            return '';
90        }
91
92        if (String(startOption).toLowerCase() === '@same@' || String(startOption).toLowerCase() === 'same') {
93            return String(pageId || '').trim();
94        }
95
96        if (String(startOption).toLowerCase() === 'start') {
97            return String(defaultStart || '').trim();
98        }
99
100        return String(startOption).trim();
101    }
102
103    function getDialog() {
104        let overlay = document.getElementById('newpagefill_overlay');
105        if (overlay) return overlay;
106
107        overlay = document.createElement('div');
108        overlay.id = 'newpagefill_overlay';
109        overlay.innerHTML = [
110            '<div class="newpagefill_dialog" role="dialog" aria-modal="true" aria-labelledby="newpagefill_title">',
111            '  <h3 id="newpagefill_title"></h3>',
112            '  <label class="newpagefill_label" for="newpagefill_input_title"></label>',
113            '  <input id="newpagefill_input_title" type="text" autocomplete="off">',
114            '  <label class="newpagefill_label" for="newpagefill_input_id"></label>',
115            '  <input id="newpagefill_input_id" type="text" autocomplete="off" autocapitalize="off" spellcheck="false">',
116            '  <div class="newpagefill_namespace_row">',
117            '    <label class="newpagefill_label" for="newpagefill_input_namespace"></label>',
118            '    <input id="newpagefill_input_namespace" type="text" autocomplete="off" autocapitalize="off" spellcheck="false">',
119            '  </div>',
120            '  <div class="newpagefill_mode_row">',
121            '    <label class="newpagefill_label" for="newpagefill_input_mode"></label>',
122            '    <select id="newpagefill_input_mode"></select>',
123            '  </div>',
124            '  <p class="newpagefill_error" aria-live="polite"></p>',
125            '  <div class="newpagefill_actions">',
126            '    <button type="button" class="newpagefill_cancel"></button>',
127            '    <button type="button" class="newpagefill_create"></button>',
128            '  </div>',
129            '</div>'
130        ].join('');
131        document.body.appendChild(overlay);
132
133        overlay.addEventListener('click', function (event) {
134            if (event.target === overlay) {
135                overlay.classList.remove('is-open');
136            }
137        });
138
139        return overlay;
140    }
141
142    function openCreatePageDialog(options) {
143        const overlay = getDialog();
144        const strings = getStrings();
145        const titleEl = overlay.querySelector('#newpagefill_title');
146        const titleLabel = overlay.querySelector('label[for="newpagefill_input_title"]');
147        const namespaceRow = overlay.querySelector('.newpagefill_namespace_row');
148        const namespaceLabel = overlay.querySelector('label[for="newpagefill_input_namespace"]');
149        const modeRow = overlay.querySelector('.newpagefill_mode_row');
150        const modeLabel = overlay.querySelector('label[for="newpagefill_input_mode"]');
151        const idLabel = overlay.querySelector('label[for="newpagefill_input_id"]');
152        const titleInput = overlay.querySelector('#newpagefill_input_title');
153        const namespaceInput = overlay.querySelector('#newpagefill_input_namespace');
154        const modeInput = overlay.querySelector('#newpagefill_input_mode');
155        const idInput = overlay.querySelector('#newpagefill_input_id');
156        const errorEl = overlay.querySelector('.newpagefill_error');
157        const cancelBtn = overlay.querySelector('.newpagefill_cancel');
158        const createBtn = overlay.querySelector('.newpagefill_create');
159        const pluginConfig = getPluginConfig();
160        const config = {
161            namespace: options && options.namespace ? options.namespace : '',
162            sepchar: options && options.sepchar ? options.sepchar : (pluginConfig.sepchar || '_'),
163            start: options && Object.prototype.hasOwnProperty.call(options, 'start')
164                ? options.start
165                : undefined,
166            defaultStart: pluginConfig.start || 'start',
167            defaultStartMode: pluginConfig.default_start_mode || 'start',
168            initialTitle: options && options.initialTitle ? options.initialTitle : '',
169            userewrite: options && typeof options.userewrite !== 'undefined'
170                ? options.userewrite
171                : pluginConfig.userewrite
172        };
173
174        titleEl.textContent = strings.title;
175        titleLabel.textContent = strings.pageTitle;
176        namespaceLabel.textContent = strings.namespace;
177        modeLabel.textContent = strings.pageMode;
178        idLabel.textContent = strings.pageId;
179        cancelBtn.textContent = strings.cancel;
180        createBtn.textContent = strings.create;
181        errorEl.textContent = '';
182        namespaceRow.style.display = config.namespace ? 'none' : '';
183        const shouldAskStartMode =
184            String(config.start).toLowerCase() === '@ask@' ||
185            String(config.start).toLowerCase() === 'ask' ||
186            ((typeof config.start === 'undefined' || config.start === null) && config.defaultStartMode === 'ask');
187
188        modeRow.style.display = shouldAskStartMode ? '' : 'none';
189        modeInput.innerHTML = '';
190        [
191            {value: 'start', label: strings.pageModeStart},
192            {value: 'none', label: strings.pageModeNone},
193            {value: '@same@', label: strings.pageModeSame}
194        ].forEach(function (option) {
195            const el = document.createElement('option');
196            el.value = option.value;
197            el.textContent = option.label;
198            modeInput.appendChild(el);
199        });
200
201        let idDirty = false;
202
203        function updateSuggestedId() {
204            if (idDirty) return;
205            idInput.value = slugifyTitle(titleInput.value, config);
206        }
207
208        function closeDialog() {
209            overlay.classList.remove('is-open');
210            document.removeEventListener('keydown', handleKeydown);
211        }
212
213        function submitDialog() {
214            const titleValue = titleInput.value.trim();
215            const namespaceValue = namespaceInput.value.trim();
216            const idValue = idInput.value.trim();
217            if (!idValue) {
218                errorEl.textContent = strings.required;
219                idInput.focus();
220                return;
221            }
222
223            const namespace = namespaceValue || config.namespace;
224            const startOption = shouldAskStartMode ? modeInput.value : config.start;
225            const startSegment = resolveStartSegment(idValue, startOption, config.defaultStart, config.defaultStartMode);
226            let targetUrl;
227
228            if (Number(config.userewrite) === 1) {
229                const baseHref = buildBaseHref(namespace, config);
230                targetUrl = baseHref + '/' + encodeURIComponent(idValue);
231                if (startSegment) {
232                    targetUrl += '/' + encodeURIComponent(startSegment);
233                }
234            } else {
235                targetUrl = buildBaseHref('', config);
236                targetUrl = appendToUrl(targetUrl, 'id=' + encodeURIComponent(buildTargetPageId(namespace, idValue, startSegment)));
237            }
238
239            targetUrl = appendToUrl(targetUrl, 'do=edit');
240            if (titleValue) {
241                targetUrl = appendToUrl(targetUrl, 'title=' + encodeURIComponent(titleValue));
242            }
243
244            window.location.href = targetUrl;
245        }
246
247        function handleKeydown(event) {
248            if (!overlay.classList.contains('is-open')) return;
249            if (event.key === 'Escape') {
250                event.preventDefault();
251                closeDialog();
252            }
253            if (event.key === 'Enter') {
254                event.preventDefault();
255                submitDialog();
256            }
257        }
258
259        titleInput.value = config.initialTitle;
260        namespaceInput.value = config.namespace;
261        idInput.value = '';
262        idDirty = false;
263        updateSuggestedId();
264
265        titleInput.oninput = function () {
266            errorEl.textContent = '';
267            updateSuggestedId();
268        };
269
270        idInput.oninput = function () {
271            errorEl.textContent = '';
272            idDirty = idInput.value.trim() !== slugifyTitle(titleInput.value, config);
273        };
274
275        cancelBtn.onclick = closeDialog;
276        createBtn.onclick = submitDialog;
277
278        overlay.classList.add('is-open');
279        document.addEventListener('keydown', handleKeydown);
280        window.setTimeout(() => titleInput.focus(), 0);
281    }
282
283    window.NewPageFill = {
284        slugifyTitle,
285        openCreatePageDialog
286    };
287})();
288