1 // PukiWiki - Yet another WikiWikiWeb clone.
3 // Copyright 2017 PukiWiki Development Team
4 // License: GPL v2 or (at your option) any later version
6 // PukiWiki JavaScript client script
7 window.addEventListener && window.addEventListener('DOMContentLoaded', function() { // eslint-disable-line no-unused-expressions
10 * @param {NodeList} nodeList
11 * @param {function(Node, number): void} func
13 function forEach(nodeList, func) {
14 if (nodeList.forEach) {
15 nodeList.forEach(func);
17 for (var i = 0, n = nodeList.length; i < n; i++) {
23 function setYourName() {
24 var NAME_KEY_ID = 'pukiwiki_comment_plugin_name';
25 var actionPathname = null;
26 function getPathname(formAction) {
27 if (actionPathname) return actionPathname;
29 var u = new URL(formAction, document.location);
30 var u2 = new URL('./', u);
31 actionPathname = u2.pathname;
34 // Note: Internet Explorer doesn't support URL class
35 var m = formAction.match(/^https?:\/\/([^/]+)(\/([^?&]+\/)?)/);
37 actionPathname = m[2]; // pathname
41 return actionPathname;
44 function getNameKey(form) {
45 var pathname = getPathname(form.action);
46 var key = 'path.' + pathname + '.' + NAME_KEY_ID;
49 function getForm(element) {
50 if (element.form && element.form.tagName === 'FORM') {
53 var e = element.parentElement;
54 for (var i = 0; i < 5; i++) {
55 if (e.tagName === 'FORM') {
62 function handleCommentPlugin(form) {
63 var namePrevious = '';
64 var nameKey = getNameKey(form);
65 if (typeof localStorage !== 'undefined') {
66 namePrevious = localStorage[nameKey];
68 var onFocusForm = function () {
69 if (form.name && !form.name.value && namePrevious) {
70 form.name.value = namePrevious;
73 var addOnForcusForm = function(eNullable) {
74 if (!eNullable) return;
75 if (eNullable.addEventListener) {
76 eNullable.addEventListener('focus', onFocusForm);
80 var textList = form.querySelectorAll('input[type=text],textarea');
81 textList.forEach(function (v) {
85 form.addEventListener('submit', function() {
86 if (typeof localStorage !== 'undefined') {
87 localStorage[nameKey] = form.name.value;
91 function setNameForComment() {
92 if (!document.querySelectorAll) return;
93 var elements = document.querySelectorAll(
94 'input[type=hidden][name=plugin][value=comment],' +
95 'input[type=hidden][name=plugin][value=pcomment],' +
96 'input[type=hidden][name=plugin][value=article],' +
97 'input[type=hidden][name=plugin][value=bugtrack]');
98 for (var i = 0; i < elements.length; i++) {
99 var form = getForm(elements[i]);
101 handleCommentPlugin(form);
108 function autoTicketLink() {
109 var headReText = '([\\s\\b]|^)';
110 var tailReText = '\\b';
111 var ignoreTags = ['A', 'INPUT', 'TEXTAREA', 'BUTTON',
112 'SCRIPT', 'FRAME', 'IFRAME'];
113 var ticketSiteList = [];
114 function regexEscape(key) {
115 return key.replace(/[-.]/g, function (m) {
119 function setupSites(siteList) {
120 for (var i = 0, length = siteList.length; i < length; i++) {
121 var site = siteList[i];
125 reText = '(' + regexEscape(site.key) + '):([A-Z][A-Z0-9_]+-\\d+)';
128 reText = '(' + regexEscape(site.key) + '):(\\d+)';
131 reText = '(' + regexEscape(site.key) + '):([0-9a-f]{7,40})';
136 site.reText = reText;
137 site.re = new RegExp(headReText + reText + tailReText);
140 function getSiteListFromBody() {
141 var defRoot = document.querySelector('#pukiwiki-site-properties .ticketlink-def');
142 if (defRoot && defRoot.value) {
143 var list = JSON.parse(defRoot.value);
149 function getSiteList() {
150 return ticketSiteList;
152 function ticketToLink(keyText) {
153 var siteList = getSiteList();
154 for (var i = 0; i < siteList.length; i++) {
155 var site = siteList[i];
156 var m = keyText.match(site.re);
158 var title = site.title;
159 var ticketKey = m[3];
161 title = title.replace(/\$1/g, ticketKey);
164 url: site.base_url + m[3],
171 function getRegex(list) {
173 for (var i = 0, length = list.length; i < length; i++) {
174 if (reText.length > 0) {
177 reText += list[i].reText;
179 return new RegExp(headReText + '(' + reText + ')' + tailReText);
181 function makeTicketLink(element) {
182 var siteList = getSiteList();
183 if (!siteList || siteList.length === 0) {
186 var re = getRegex(siteList);
189 var text = element.nodeValue;
190 while (m = text.match(re)) { // eslint-disable-line no-cond-assign
191 // m[1]: head, m[2]: keyText
193 f = document.createDocumentFragment();
195 if (m.index > 0 || m[1].length > 0) {
196 f.appendChild(document.createTextNode(text.substr(0, m.index) + m[1]));
198 var a = document.createElement('a');
199 a.textContent = m[2];
200 var linkInfo = ticketToLink(a.textContent);
201 a.href = linkInfo.url;
202 a.title = linkInfo.title;
204 text = text.substr(m.index + m[0].length);
207 if (text.length > 0) {
208 f.appendChild(document.createTextNode(text));
210 element.parentNode.replaceChild(f, element);
213 function walkElement(element) {
214 var e = element.firstChild;
216 if (e.nodeType === 3 && e.nodeValue &&
217 e.nodeValue.length > 5 && /\S/.test(e.nodeValue)) {
218 var next = e.nextSibling;
222 if (e.nodeType === 1 && ignoreTags.indexOf(e.tagName) === -1) {
229 if (!Array.prototype.indexOf || !document.createDocumentFragment) {
232 ticketSiteList = getSiteListFromBody();
233 var target = document.getElementById('body');
236 function confirmEditFormLeaving() {
238 if (typeof s !== 'string') {
241 return s.replace(/^\s+|\s+$/g, '');
243 if (!document.querySelector) return;
244 var canceled = false;
245 var pluginNameE = document.querySelector('#pukiwiki-site-properties .plugin-name');
246 if (!pluginNameE) return;
247 var originalText = null;
248 if (pluginNameE.value !== 'edit') return;
249 var editForm = document.querySelector('.edit_form form._plugin_edit_edit_form');
250 if (!editForm) return;
251 var cancelMsgE = editForm.querySelector('#_msg_edit_cancel_confirm');
252 var unloadBeforeMsgE = editForm.querySelector('#_msg_edit_unloadbefore_message');
253 var textArea = editForm.querySelector('textarea[name="msg"]');
254 if (!textArea) return;
255 originalText = textArea.value;
256 var cancelForm = document.querySelector('.edit_form form._plugin_edit_cancel');
257 var submited = false;
258 editForm.addEventListener('submit', function() {
262 cancelForm.addEventListener('submit', function(e) {
265 if (trim(textArea.value) === trim(originalText)) {
269 var message = 'The text you have entered will be discarded. Is it OK?';
270 if (cancelMsgE && cancelMsgE.value) {
271 message = cancelMsgE.value;
273 if (window.confirm(message)) { // eslint-disable-line no-alert
281 window.addEventListener('beforeunload', function(e) {
282 if (canceled) return;
283 if (submited) return;
284 if (trim(textArea.value) === trim(originalText)) return;
285 var message = 'Data you have entered will not be saved.';
286 if (unloadBeforeMsgE && unloadBeforeMsgE.value) {
287 message = unloadBeforeMsgE.value;
289 e.returnValue = message;
292 function showPagePassage() {
295 * @param {string} dateText
297 function getPassage(dateText, now) {
301 var units = [{u: 'm', max: 60}, {u: 'h', max: 24}, {u: 'd', max: 1}];
303 d.setTime(Date.parse(dateText));
304 var t = (now.getTime() - d.getTime()) / (1000 * 60); // minutes
305 var unit = units[0].u; var card = units[0].max;
306 for (var i = 0; i < units.length; i++) {
307 unit = units[i].u; card = units[i].max;
311 return '(' + Math.floor(t) + unit + ')';
313 var now = new Date();
314 var elements = document.getElementsByClassName('page_passage');
315 forEach(elements, function(e) {
316 var dt = e.getAttribute('data-mtime');
318 var d = new Date(dt);
319 e.textContent = ' ' + getPassage(d, now);
322 var links = document.getElementsByClassName('link_page_passage');
323 forEach(links, function(e) {
324 var dt = e.getAttribute('data-mtime');
326 var d = new Date(dt);
328 e.title = e.title + ' ' + getPassage(d, now);
330 e.title = e.textContent + ' ' + getPassage(d, now);
337 confirmEditFormLeaving();