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