s4

annotate s4-main.js @ 893:c1a5d137740d

Display momentary information of frozen state change
author HIROSE Yuuji <yuuji@gentei.org>
date Sat, 26 Dec 2020 15:17:56 +0900
parents 5843755e3b30
children 1dc4007d7c20
rev   line source
yuuji@889 1 // 愛
yuuji@586 2 (function (){
yuuji@667 3 function collectElementsByAttr(elm, attr, val) {
yuuji@586 4 var e = document.getElementsByTagName(elm);
yuuji@586 5 if (!e) return null;
yuuji@586 6 var list = [];
yuuji@586 7 for (var i of e) {
yuuji@667 8 if (i.getAttribute(attr) == val)
yuuji@586 9 list.push(i)
yuuji@586 10 }
yuuji@586 11 return list;
yuuji@586 12 }
yuuji@675 13 function nthChildOf(parent, n, elem) { // Return Nth child of type ELEM
yuuji@675 14 // N begins with 1
yuuji@675 15 var i=0;
yuuji@675 16 var le = elem.toLowerCase();
yuuji@675 17 for (var c of parent.childNodes) {
yuuji@675 18 if (!c.tagName) continue;
yuuji@675 19 if (c.tagName.toLowerCase() == le) {
yuuji@675 20 if (++i >= n) return c;
yuuji@675 21 }
yuuji@675 22 }
yuuji@675 23 return null;
yuuji@675 24 }
yuuji@586 25 function insertRedirect(e) {
yuuji@586 26 var articleId, textarea = document.getElementById("text");
yuuji@586 27 var p = e.target, checked = p.checked;
yuuji@586 28 while (p = p.parentNode)
yuuji@586 29 if (p.nodeName.match(/^td$/i)) break;
yuuji@586 30 if (!p) return;
yuuji@586 31 while (p = p.nextSibling)
yuuji@586 32 if (p.nodeName.match(/^td$/i)) break;
yuuji@586 33 if (!p) return;
yuuji@586 34 articleId = p.getAttribute("id");
yuuji@586 35 if (textarea && articleId) {
yuuji@586 36 var tv = textarea.value, lines;
yuuji@586 37 if (tv)
yuuji@586 38 lines = tv.split("\n");
yuuji@586 39 else
yuuji@586 40 lines = [""];
yuuji@586 41 var re = new RegExp("[, ]*#"+articleId+"(?![0-9])");
yuuji@590 42 checked = (p.nodeName.match(/^input$/)
yuuji@590 43 ? p.checked // checkbox obeys its status
yuuji@590 44 : !lines[0].match(re)) // a-elment toggles redirection
yuuji@586 45 if (checked) {
yuuji@586 46 if (!lines[0].match(re)) {
yuuji@586 47 var re2 = new RegExp(/>#[#0-9, ]+[0-9]/);
yuuji@586 48 if (lines[0].match(re2))
yuuji@586 49 lines[0] = lines[0].replace(
yuuji@586 50 re2, '$&, '+'#'+articleId);
yuuji@586 51 else {
yuuji@586 52 if (lines[0] > "") lines[0] = " "+lines[0];
yuuji@586 53 lines[0] = ">#"+articleId+lines[0];
yuuji@586 54 }
yuuji@586 55 }
yuuji@586 56 } else { // Remove #xxxxx
yuuji@586 57 if (lines[0].match(/^>#[0-9 ,]+#/)) // 2 or more #id's
yuuji@586 58 lines[0] = lines[0].replace(
yuuji@586 59 new RegExp("^>#"+articleId+"[ ,]*"), ">").replace(
yuuji@586 60 new RegExp("[ ,]*#"+articleId), "");
yuuji@586 61 else {
yuuji@586 62 lines[0] = lines[0].replace(
yuuji@586 63 new RegExp(">#"+articleId+"[ ,]*"), "");
yuuji@586 64 }
yuuji@586 65 }
yuuji@586 66 lines[0] = lines[0].replace(/^> *$/, '');
yuuji@586 67 textarea.value = lines.join("\n");
yuuji@586 68 }
yuuji@586 69 }
yuuji@659 70 function reverseChecks() {
yuuji@667 71 var names = collectElementsByAttr("input", "name", "usel");
yuuji@659 72 for (let u of names) {
yuuji@659 73 u.checked = !u.checked;
yuuji@659 74 }
yuuji@659 75 }
yuuji@852 76 function renumberOL(str, start) {
yuuji@852 77 var stra = str.split("\n");
yuuji@852 78
yuuji@852 79 for (var i=1; i<stra.length; i++) {
yuuji@852 80 if (stra[i].match(/^[1-9][0-9]*\. /)) {
yuuji@852 81 let orig=stra[i];
yuuji@852 82 stra[i] = (++start)+". "+RegExp.rightContext;
yuuji@852 83 } else if (stra[i].match(/^ /)) {
yuuji@852 84 continue;
yuuji@852 85 } else
yuuji@852 86 break;
yuuji@852 87 }
yuuji@852 88 return stra.join("\n");
yuuji@852 89 }
yuuji@852 90 function submitThisForm(input) {
yuuji@852 91 for (var elm=input.parentNode; elm; elm = elm.parentNode) {
yuuji@852 92 if (elm.nodeName.match(/form/i)) {
yuuji@852 93 elm.submit();
yuuji@852 94 return true;
yuuji@852 95 }
yuuji@852 96 }
yuuji@852 97 return false;
yuuji@852 98 }
yuuji@852 99 function helpMarkdownBS(e) {
yuuji@852 100 var area = e.target, pos = area.selectionStart, text = area.value;
yuuji@852 101 if (area.selectionStart != area.selectionEnd) return;
yuuji@852 102 if (pos<2) return;
yuuji@852 103 if (text.substr(pos-1, 2)=="\n\n") return;
yuuji@852 104 var bol = text.lastIndexOf("\n", pos-1),
yuuji@852 105 eol = text.indexOf("\n", pos);
yuuji@852 106 if (bol<=0 || bol==eol) return;
yuuji@852 107 var thisline = text.substring(bol+1, eol==-1 ? text.length : eol);
yuuji@852 108 thisline = text.substring(bol+1, pos);
yuuji@852 109 if (thisline == "* ") {
yuuji@852 110 area.setSelectionRange(pos-2, pos);
yuuji@852 111 } else if (thisline.match(/^[1-9][0-9]*\. $/)) {
yuuji@852 112 area.setSelectionRange(pos-RegExp.lastMatch.length, pos);
yuuji@852 113 }
yuuji@852 114 }
yuuji@852 115 function helpMarkdownEnter(e) {
yuuji@852 116 if (e.keyCode == 13 && !e.shiftKey) {
yuuji@852 117 if (e.metaKey && submitThisForm(e.target)) {
yuuji@852 118 e.preventDefault();
yuuji@852 119 return;
yuuji@852 120 }
yuuji@846 121 var area = e.target;
yuuji@846 122 var pos = area.selectionStart, text = area.value;
yuuji@847 123 if (pos==0) return;
yuuji@846 124 var last = text.lastIndexOf("\n", pos-1);
yuuji@852 125 var rest = text.substring(pos), rest0=rest;
yuuji@852 126 var line = last ? text.substring(last+1, pos) : text;
yuuji@852 127 var next = rest.substring(rest.indexOf("\n"))||rest;
yuuji@852 128 next=next.substring(1);
yuuji@847 129 var tail = text.substring(pos-2, pos), br = (tail==" ");
yuuji@847 130 var add = "", offset = 1;
yuuji@846 131 if (line.startsWith("* ")) {
yuuji@847 132 add = "* ";
yuuji@847 133 offset += add.length;
yuuji@847 134 if (br) {
yuuji@847 135 add = " " + "\n" + add;
yuuji@847 136 }
yuuji@846 137 } else if (line.match(/^([1-9][0-9]*)\. /)) {
yuuji@852 138 var ln = parseInt(RegExp.$1), nn=ln+1,
yuuji@852 139 len = RegExp.lastMatch.length;
yuuji@852 140 add = nn+". ";
yuuji@852 141 let toeol = text.substr(pos, text.indexOf("\n"));
yuuji@852 142 if (br) {
yuuji@852 143 if (next.startsWith(add)) {
yuuji@852 144 add=" ".repeat(len);
yuuji@852 145 nn = ln;
yuuji@852 146 } else {
yuuji@852 147 add = " ".repeat(len)+ "\n" + add;
yuuji@852 148 offset -= len+1;
yuuji@852 149 }
yuuji@852 150 }
yuuji@852 151 if (next.match(/^[1-9][0-9]*\. /))
yuuji@852 152 rest = renumberOL(rest, nn);
yuuji@847 153 offset += add.length;
yuuji@852 154 } else if (line.match(/^\|( *).+\|/)) {
yuuji@846 155 add = "|" + RegExp.$1 + " |";
yuuji@847 156 offset += add.length-2;
yuuji@847 157 } else {
yuuji@847 158 return;
yuuji@846 159 }
yuuji@847 160 e.preventDefault();
yuuji@852 161 if (!document.execCommand("insertText", false, "\n"+add)) {
yuuji@852 162 //Firefox
yuuji@852 163 area.selectionEnd = area.value.length;
yuuji@852 164 area.setRangeText("\n"+add+rest);
yuuji@852 165 area.selectionEnd = null;
yuuji@852 166 } else {
yuuji@852 167 area.selectionEnd = area.value.length;
yuuji@852 168 area.setSelectionRange(area.selectionStart, area.value.length);
yuuji@852 169 document.execCommand("insertText", false, rest);
yuuji@852 170 area.selectionEnd = null;
yuuji@852 171 area.focus();
yuuji@852 172 }
yuuji@852 173 area.selectionStart = pos+offset;
yuuji@852 174 return;
yuuji@852 175 if (document.execCommand("insertText", false, "\n"+add)) {
yuuji@852 176 //area.setSelectionRange(area.selectionStart, text.length);
yuuji@852 177 // alert("rest=["+rest+"], add=["+add+"]");
yuuji@852 178 alert(text.substring(pos, area.value.length));
yuuji@852 179 if (rest != rest0) {
yuuji@852 180 area.setSelectionRange(pos, area.value.length);
yuuji@852 181 return;
yuuji@852 182 document.execCommand("delete");
yuuji@852 183 }
yuuji@852 184 document.execCommand("insertText", false, rest);
yuuji@852 185 } else {
yuuji@852 186 // Firefox cannot use insertText in textarea...
yuuji@852 187 area.value = text.substring(0, pos) + "\n" + add + rest;
yuuji@852 188 }
yuuji@846 189 //area.setSelectionRange(pos+length(add));
yuuji@847 190 area.selectionStart=area.selectionEnd = (pos + offset);
yuuji@847 191
yuuji@846 192 }
yuuji@846 193 }
yuuji@852 194 function helpMarkdown(e) {
yuuji@852 195 switch (e.keyCode) {
yuuji@852 196 case 8: helpMarkdownBS(e); break;
yuuji@852 197 case 13: helpMarkdownEnter(e); break;
yuuji@852 198 }
yuuji@852 199 }
yuuji@846 200 /* Init event listeners */
yuuji@837 201 function addFileInput() {
yuuji@837 202 var inpfile = collectElementsByAttr("input", "name", "image");
yuuji@837 203 if (!inpfile) return;
yuuji@837 204 var filled = true;
yuuji@837 205 var i, ih;
yuuji@837 206 for (i of inpfile) {
yuuji@837 207 if (! i.value) filled=false;
yuuji@837 208 }
yuuji@837 209 if (filled) {
yuuji@837 210 ih = i.parentNode.innerHTML;
yuuji@837 211 if (ih) {
yuuji@837 212 var inpf = ih.substring(ih.indexOf("<input")),
yuuji@837 213 newi = "<br>"+inpf.substring(0, inpf.indexOf(">")+1);
yuuji@837 214 i.insertAdjacentHTML("afterend", newi)
yuuji@837 215 // alert(newi);
yuuji@837 216 }
yuuji@837 217 }
yuuji@837 218 }
yuuji@837 219 function initFileInput() { // Multiplies "input type=file"
yuuji@837 220 var el, morefile = document.getElementById("morefile");
yuuji@837 221 if (morefile) {
yuuji@837 222 for (el of collectElementsByAttr("input", "name", "image")) {
yuuji@837 223 el.addEventListener("change", function(ev) {
yuuji@837 224 if (ev.target.value > "" && ev.target.files.length == 1)
yuuji@837 225 morefile.style.visibility = "visible";
yuuji@837 226 // No need to hide again, sure?
yuuji@837 227 });
yuuji@837 228 }
yuuji@837 229 morefile.addEventListener("click", addFileInput, null);
yuuji@837 230 }
yuuji@837 231 // When renaming, select basename part
yuuji@837 232 for (el of collectElementsByAttr("input", "class", "mv")) {
yuuji@837 233 el.addEventListener("focus", function(ev) {
yuuji@837 234 var i = ev.target;
yuuji@837 235 if (i) {
yuuji@837 236 i.setSelectionRange(0, i.value.lastIndexOf("."));
yuuji@837 237 }
yuuji@837 238 });
yuuji@837 239 }
yuuji@837 240 }
yuuji@846 241 function initTextarea() {
yuuji@846 242 var te = collectElementsByAttr("textarea", "name", "text");
yuuji@846 243 if (!te || !te[0]) return;
yuuji@846 244 te[0].addEventListener("keydown", helpMarkdown, false);
yuuji@846 245 }
yuuji@659 246 function initBlogs() {
yuuji@837 247 // Auto-complete #xxxx
yuuji@837 248 var check = collectElementsByAttr("input", "name", "notifyto");
yuuji@586 249 if (check)
yuuji@590 250 for (let i of check) {
yuuji@586 251 i.addEventListener("click", insertRedirect, null);
yuuji@586 252 }
yuuji@590 253 for (let i of document.getElementsByTagName("a"))
yuuji@590 254 if (i.getAttribute("href").match(/^#[0-9]+$/))
yuuji@590 255 if (RegExp.lastMatch == i.innerHTML)
yuuji@590 256 i.addEventListener("click", insertRedirect, null)
yuuji@586 257 }
yuuji@659 258 function initGrpAction() {
yuuji@659 259 var rev = document.getElementById("reverse");
yuuji@667 260 if (!rev) return; // Is not grpAction page
yuuji@667 261 if (rev.tagName.match(/span/i)) {
yuuji@659 262 rev.textContent = " 反転 ";
yuuji@659 263 rev.addEventListener("click", reverseChecks, null);
yuuji@659 264 }
yuuji@667 265 var emailbtn = document.getElementById("email");
yuuji@667 266 emailbtn.addEventListener("click", function(ev){
yuuji@675 267 // Enlarge box and Select user's checkbox
yuuji@667 268 if (!ev.target.checked) return;
yuuji@673 269 var x = collectElementsByAttr("div", "class", "foldtabs");
yuuji@673 270 if (x && x[0] && x[0].style) {
yuuji@673 271 x[0].style.height = "10em";
yuuji@673 272 }
yuuji@667 273 let myuid = document.getElementById("myuid");
yuuji@667 274 if (myuid) {
yuuji@667 275 let usel = collectElementsByAttr("input", "name", "usel");
yuuji@667 276 if (usel) {
yuuji@667 277 for (u of usel) {
yuuji@667 278 if (u.value == myuid.value)
yuuji@667 279 u.checked = true;
yuuji@667 280 }
yuuji@667 281 }
yuuji@667 282 }
yuuji@667 283 }, null);
yuuji@675 284 var teamsel = document.getElementById("selteam");
yuuji@675 285 if (teamsel) {
yuuji@675 286 var usel, p, team;
yuuji@675 287 // Select all members of the team
yuuji@675 288 teamsel.addEventListener("change", function(ev) {
yuuji@675 289 var teamname = teamsel.value,
yuuji@676 290 selected = new RegExp('(^| )'+teamname+"($|,)");
yuuji@675 291 usel = collectElementsByAttr("input", "name", "usel");
yuuji@675 292 if (!usel) return;
yuuji@675 293 for (u of usel) {
yuuji@675 294 p = u.parentNode; // should be label
yuuji@675 295 if (!p) continue;
yuuji@675 296 if (teamname == "TEAM") { // Reset all checks
yuuji@675 297 u.checked = false; // when "TEAM" is selected
yuuji@675 298 } else {
yuuji@675 299 p = p.parentNode.parentNode;// should be tr
yuuji@675 300 team = nthChildOf(p, 3, "td")
yuuji@675 301 if (team && team.textContent
yuuji@675 302 && team.textContent.match(selected)) {
yuuji@675 303 u.checked = true;
yuuji@675 304 }
yuuji@675 305 }
yuuji@675 306 }
yuuji@675 307 }, null);
yuuji@675 308 }
yuuji@659 309 }
yuuji@893 310 function dispInfoMomentary(msg, elem) {
yuuji@893 311 // Momentarily display MSG in tooltip-baloon relative to ELEM element.
yuuji@893 312 let help = document.createElement("p");
yuuji@893 313 elem.style.position = 'relative';
yuuji@893 314 elem.style.overflow = 'visible';
yuuji@893 315 help.setAttribute("class", "info-tooltip");
yuuji@893 316 help.innerHTML = msg;
yuuji@893 317 elem.appendChild(help);
yuuji@893 318 setTimeout(() => {
yuuji@893 319 help.classList.add("dissolving");
yuuji@893 320 setTimeout(() => help.remove(), 3000);
yuuji@893 321 }, 1000);
yuuji@893 322 }
yuuji@889 323 function initGrphome() {
yuuji@889 324 console.log("initGrphome");
yuuji@889 325 let btn = document.querySelectorAll("button.toggle-frozen");
yuuji@889 326 if (!btn) return;
yuuji@889 327 let url = document.URL,
yuuji@889 328 mypath = url.substring(url.lastIndexOf("/"));
yuuji@889 329 if (mypath.match(/(.*)\/(.*)/)) {
yuuji@889 330 mypath = RegExp.$2;
yuuji@889 331 mypath = mypath.substring(0, mypath.lastIndexOf("?"));
yuuji@889 332 //alert("mypath="+mypath);
yuuji@889 333 } else return;
yuuji@893 334 var ja = navigator.language.match(/ja/i);
yuuji@889 335
yuuji@889 336 function toggleFrozen(e, rowid) {
yuuji@889 337 let tgt = mypath+"?blog_setfrozen+"+rowid;
yuuji@893 338 let td = e.target.parentNode;
yuuji@893 339 let tr = td.parentNode;
yuuji@889 340 fetch(tgt, {
yuuji@889 341 method: "POST",
yuuji@889 342 headers: {'Content-Type': 'text/html; charset=utf-8'},
yuuji@889 343 }).then(function(resp) {
yuuji@889 344 return resp.text();
yuuji@889 345 }).then(function(tbody) {
yuuji@889 346 try {
yuuji@889 347 var json = JSON.parse(tbody);
yuuji@889 348 } catch (e) {
yuuji@889 349 return;
yuuji@889 350 }
yuuji@893 351 let state = json.state, newstate, info;
yuuji@889 352 if (json.alert) {
yuuji@889 353 alert(json.alert)
yuuji@889 354 }
yuuji@889 355 if (state.match(/frozen/i)) {
yuuji@889 356 newstate = "凍結";
yuuji@893 357 info = ja ? newstate+"に設定しました" : 'Set Frozen';
yuuji@889 358 } else {
yuuji@889 359 newstate = null;
yuuji@893 360 info = ja ? '稼動に設定しました' : 'Set Running';
yuuji@889 361 }
yuuji@889 362 tr.setAttribute("class", newstate);
yuuji@893 363 dispInfoMomentary(info, td);
yuuji@889 364 });
yuuji@889 365 }
yuuji@889 366 for (let b of btn) {
yuuji@889 367 let rowid = null;
yuuji@893 368 let td=b.parentNode, tr = td.parentNode, fr, ru;
yuuji@893 369 ru = ja ? "動" : "Running";
yuuji@893 370 fr = ja ? "凍" : "Frozen";
yuuji@893 371 b.setAttribute('frozen-marker', fr);
yuuji@893 372 b.setAttribute('running-marker', ru);
yuuji@889 373 for (let a of tr.querySelectorAll("a[href]")) {
yuuji@889 374 if (a.getAttribute("href").match(/\?replyblog\+([0-9]+)/)) {
yuuji@889 375 rowid = parseInt(RegExp.$1);
yuuji@889 376 break;
yuuji@889 377 }
yuuji@889 378 }
yuuji@889 379 if (rowid && rowid>0) {
yuuji@889 380 b.addEventListener("click", function(e) {
yuuji@889 381 if (!btn) return;
yuuji@889 382 toggleFrozen(e, rowid);
yuuji@889 383 }, false);
yuuji@889 384 b.setAttribute("title", "稼動/凍結をその場で切り替えます\n\
yuuji@889 385 Toggle Running/Frozen ("+rowid+")");
yuuji@889 386 }
yuuji@889 387 }
yuuji@889 388 }
yuuji@659 389 function init() {
yuuji@659 390 initGrpAction();
yuuji@659 391 initBlogs();
yuuji@837 392 initFileInput();
yuuji@846 393 initTextarea();
yuuji@889 394 initGrphome();
yuuji@659 395 }
yuuji@586 396 document.addEventListener('DOMContentLoaded', init, null);
yuuji@586 397 })();