index.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. function lang(message = '') {
  2. var keys = ( message.length ? message.split('.') : [] );
  3. var text = i18n[0];
  4. var fallback = 1;
  5. for ( var n = 0; n < keys.length; n++ ) {
  6. if ( text ) {
  7. text = text[keys[n]];
  8. if ( typeof text === 'string' ) text = text.trim();
  9. }
  10. if ( !text ) {
  11. if ( fallback < i18n.length ) {
  12. text = i18n[fallback];
  13. fallback++;
  14. n = -1;
  15. }
  16. else {
  17. n = keys.length;
  18. }
  19. }
  20. }
  21. return ( text || '⧼' + message + '⧽' );
  22. }
  23. var baseSelect = document.getElementsByTagName('select');
  24. for ( var b = 0; b < baseSelect.length; b++ ) {
  25. if ( baseSelect[b].id === 'wb-settings-lang' ) {
  26. /** @type {HTMLImageElement} */
  27. const langWidget = document.getElementById('wb-settings-lang-widget');
  28. if ( langWidget ) {
  29. var widgetPath = 'widgets';
  30. if ( document.location.pathname.split('/')[3] === 'rcscript' ) {
  31. widgetPath = 'widgets/RcGcDb';
  32. }
  33. langWidget.src = `/src/${widgetPath}/${baseSelect[b].value}.png`;
  34. langWidget.alt = baseSelect[b].selectedOptions.item(0).textContent;
  35. baseSelect[b].addEventListener( 'input', function() {
  36. langWidget.src = `/src/${widgetPath}/${this.value}.png`;
  37. langWidget.alt = this.selectedOptions.item(0).textContent;
  38. } );
  39. }
  40. }
  41. if ( baseSelect[b].parentElement.parentElement.querySelector('button.addmore') ) {
  42. baseSelect[b].addEventListener( 'input', toggleOption );
  43. toggleOption.call(baseSelect[b]);
  44. }
  45. }
  46. /** @type {HTMLCollectionOf<HTMLButtonElement>} */
  47. var addmore = document.getElementsByClassName('addmore');
  48. for ( var j = 0; j < addmore.length; j++ ) {
  49. /** @this HTMLButtonElement */
  50. addmore[j].onclick = function() {
  51. /** @type {HTMLDivElement} */
  52. var clone = this.previousElementSibling.cloneNode(true);
  53. clone.classList.add('wb-settings-additional-select');
  54. if ( clone.firstElementChild.tagName === 'LABEL' ) clone.removeChild(clone.firstElementChild);
  55. /** @type {HTMLSelectElement} */
  56. var cloneSelect = clone.firstElementChild;
  57. var newName = cloneSelect.name.replace( /^([a-z]+-)(\d)$/, function(fullname, base, id) {
  58. return base + (+id + 1);
  59. } );
  60. cloneSelect.name = newName;
  61. cloneSelect.removeAttribute('id');
  62. cloneSelect.required = false;
  63. cloneSelect.childNodes.forEach( function(child) {
  64. child.hidden = false;
  65. child.selected = false;
  66. child.defaultSelected = false;
  67. } );
  68. cloneSelect.querySelector('option.defaultSelect').defaultSelected = true;
  69. cloneSelect.querySelector('option.defaultSelect').selected = true;
  70. cloneSelect.addEventListener( 'input', toggleOption );
  71. cloneSelect.name
  72. cloneSelect.htmlFor
  73. cloneSelect.id
  74. if ( clone.children.length === 5 ) {
  75. clone.children.item(1).name = newName + '-change';
  76. clone.children.item(1).id = 'wb-settings-' + newName + '-add';
  77. clone.children.item(1).checked = false;
  78. clone.children.item(2).htmlFor = 'wb-settings-' + newName + '-add';
  79. clone.children.item(3).name = newName + '-change';
  80. clone.children.item(3).id = 'wb-settings-' + newName + '-remove';
  81. clone.children.item(3).checked = false;
  82. clone.children.item(4).htmlFor = 'wb-settings-' + newName + '-remove';
  83. clone.children.item(1).defaultChecked = true;
  84. clone.children.item(1).checked = true;
  85. }
  86. this.before(clone);
  87. toggleOption.call(cloneSelect);
  88. };
  89. }
  90. /** @this HTMLSelectElement */
  91. function toggleOption() {
  92. /** @type {HTMLOptionElement[]} */
  93. var options = [];
  94. /** @type {HTMLOptionElement[]} */
  95. var selected = [];
  96. var allSelect = this.parentElement.parentElement.querySelectorAll('select');
  97. allSelect.forEach( function(select) {
  98. options.push(...select.options);
  99. selected.push(...select.selectedOptions);
  100. } );
  101. /** @type {HTMLButtonElement} */
  102. var button = this.parentElement.parentElement.querySelector('button.addmore');
  103. if ( selected.some( function(option) {
  104. if ( option && option.value ) return false;
  105. else return true;
  106. } ) || allSelect.length >= 10 || allSelect.length >= this.options.length-1 ) {
  107. button.hidden = true;
  108. }
  109. else button.hidden = false;
  110. var selectedValues = selected.filter( function(option) {
  111. if ( option && option.value ) return true;
  112. else return false;
  113. } ).map( function(option) {
  114. return option.value;
  115. } );
  116. options.forEach( function(option) {
  117. if ( selectedValues.includes( option.value ) && !option.selected ) {
  118. option.disabled = true;
  119. }
  120. else if ( option.disabled ) option.disabled = false;
  121. } );
  122. }
  123. var divTemp = document.createElement('div');
  124. divTemp.innerHTML = '<input type="url" value="invalid">';
  125. const validationMessageInvalidURL = divTemp.firstChild.validationMessage;
  126. /** @type {HTMLInputElement} */
  127. const wiki = document.getElementById('wb-settings-wiki');
  128. if ( wiki ) {
  129. wiki.addEventListener( 'input', function() {
  130. if ( !/^(?:https?:)?\/\//.test(this.value) ) {
  131. if ( this.validity.valid ) {
  132. this.setCustomValidity(validationMessageInvalidURL);
  133. }
  134. }
  135. else this.setCustomValidity('');
  136. } );
  137. /** @type {HTMLButtonElement} */
  138. const wikicheck = document.getElementById('wb-settings-wiki-check');
  139. /** @type {HTMLDivElement} */
  140. const wikichecknotice = document.getElementById('wb-settings-wiki-check-notice');
  141. if ( wikicheck && wikichecknotice ) {
  142. wikicheck.onclick = function() {
  143. var wikinew = wiki.value.replace( /^(?:https?:)?\/\//, '' );
  144. var regex = wikinew.match( /^([a-z\d-]{1,50}\.(?:gamepedia\.com|(?:fandom\.com|wikia\.org)(?:(?!\/(?:wiki|api)\/)\/[a-z-]{2,12})?))(?:\/|$)/ );
  145. if ( regex ) wikinew = regex[1];
  146. else if ( !wiki.validity.valid ) return wiki.reportValidity();
  147. else {
  148. wikinew = wikinew.replace( /\/(?:index|api|load|rest)\.php(?:|[\?\/#].*)$/, '' ).replace( /\/$/, '' );
  149. }
  150. var readonly = wiki.readOnly;
  151. wiki.readOnly = true;
  152. wikicheck.disabled = true;
  153. fetch( '/api?wiki=' + encodeURIComponent( wikinew ), {
  154. method: 'GET',
  155. cache: 'no-cache',
  156. mode: 'same-origin',
  157. headers: {
  158. Accept: 'application/json'
  159. }
  160. } ).then( function(response) {
  161. if ( response.ok && response.status === 200 ) return response.json();
  162. else return Promise.reject('Error: The server did not respond correctly.');
  163. } ).then( function(response) {
  164. if ( !response.api ) {
  165. console.log('Error: The server did not respond correctly.');
  166. return;
  167. }
  168. wikichecknotice.className = 'notice';
  169. wikichecknotice.innerHTML = '';
  170. var noticeExtraParts = [];
  171. if ( response.sitename ) {
  172. var wikiEmbed = document.createElement('a');
  173. wikiEmbed.target = '_blank';
  174. wikiEmbed.href = response.base;
  175. var wikiEmbedDiv = document.createElement('div');
  176. wikiEmbed.append(wikiEmbedDiv);
  177. var wikiEmbedStrong = document.createElement('strong');
  178. wikiEmbedStrong.textContent = response.sitename;
  179. var wikiEmbedImg = document.createElement('img');
  180. wikiEmbedImg.src = response.logo;
  181. wikiEmbedImg.alt = response.sitename;
  182. var wikiEmbedSmall = document.createElement('small');
  183. wikiEmbedSmall.textContent = response.base;
  184. wikiEmbedDiv.append(wikiEmbedStrong, wikiEmbedImg, wikiEmbedSmall);
  185. noticeExtraParts.push(document.createElement('hr'), wikiEmbed);
  186. }
  187. if ( response.error ) {
  188. wiki.setCustomValidity(lang('invalid.title'));
  189. wikichecknotice.classList.add('notice-error');
  190. var noticeTitle = document.createElement('b');
  191. noticeTitle.textContent = lang('invalid.title');
  192. var noticeText = document.createElement('div');
  193. noticeText.textContent = lang('invalid.text');
  194. var noticeNote = '';
  195. if ( response.error_code ) {
  196. noticeNote = document.createElement('div');
  197. noticeNote.textContent = lang('invalid.note_' + response.error_code);
  198. }
  199. wikichecknotice.append(noticeTitle, noticeText, noticeNote, ...noticeExtraParts);
  200. return;
  201. }
  202. if ( !readonly ) wiki.value = response.wiki;
  203. if ( document.location.pathname.split('/')[3] === 'rcscript' ) {
  204. if ( !response.MediaWiki ) {
  205. wiki.setCustomValidity(lang('outdated.title'));
  206. wikichecknotice.classList.add('notice-error');
  207. var noticeTitle = document.createElement('b');
  208. noticeTitle.textContent = lang('outdated.title');
  209. var noticeText = document.createElement('div');
  210. noticeText.textContent = lang('outdated.text');
  211. var noticeLink = document.createElement('a');
  212. noticeLink.target = '_blank';
  213. noticeLink.href = 'https://www.mediawiki.org/wiki/MediaWiki_1.30';
  214. noticeLink.textContent = 'https://www.mediawiki.org/wiki/MediaWiki_1.30';
  215. wikichecknotice.append(noticeTitle, noticeText, noticeLink, ...noticeExtraParts);
  216. return;
  217. }
  218. if ( response.RcGcDw !== document.location.pathname.split('/')[2] && ( document.location.pathname.split('/')[4] === 'new' || wiki.value !== wiki.defaultValue ) ) {
  219. wikichecknotice.classList.add('notice-info');
  220. var noticeTitle = document.createElement('b');
  221. noticeTitle.textContent = lang('sysmessage.title');
  222. var sysmessageLink = document.createElement('a');
  223. sysmessageLink.target = '_blank';
  224. sysmessageLink.href = response.customRcGcDw;
  225. var sysmessageCode = document.createElement('code');
  226. sysmessageCode.textContent = 'MediaWiki:Custom-RcGcDw';
  227. sysmessageLink.append(sysmessageCode);
  228. var guildCode = document.createElement('code');
  229. guildCode.className = 'user-select';
  230. guildCode.textContent = document.location.pathname.split('/')[2];
  231. var noticeText = document.createElement('div');
  232. var textSnippets = lang('sysmessage.text').split(/\$\d/);
  233. noticeText.append(
  234. document.createTextNode(textSnippets[0]),
  235. sysmessageLink,
  236. document.createTextNode(textSnippets[1]),
  237. guildCode,
  238. document.createTextNode(textSnippets[2])
  239. );
  240. var noticeLink = sysmessageLink.cloneNode();
  241. noticeLink.textContent = response.customRcGcDw;
  242. wikichecknotice.append(noticeTitle, noticeText, noticeLink, ...noticeExtraParts);
  243. return;
  244. }
  245. wikichecknotice.classList.add('notice-success');
  246. var noticeTitle = document.createElement('b');
  247. noticeTitle.textContent = lang('valid.title');
  248. wikichecknotice.append(noticeTitle, ...noticeExtraParts);
  249. return;
  250. }
  251. wikichecknotice.classList.add('notice-success');
  252. var noticeTitle = document.createElement('b');
  253. noticeTitle.textContent = lang('valid.title');
  254. wikichecknotice.append(noticeTitle);
  255. if ( !/\.(?:gamepedia\.com|fandom\.com|wikia\.org)$/.test(wiki.value.split('/')[2]) ) {
  256. if ( !response.MediaWiki ) {
  257. var noticeLink = document.createElement('a');
  258. noticeLink.target = '_blank';
  259. noticeLink.href = 'https://www.mediawiki.org/wiki/MediaWiki_1.30';
  260. noticeLink.textContent = 'MediaWiki 1.30';
  261. var noticeText = document.createElement('div');
  262. var textSnippets = lang('valid.MediaWiki').split(/\$\d/);
  263. noticeText.append(
  264. document.createTextNode(textSnippets[0]),
  265. noticeLink,
  266. document.createTextNode(textSnippets[1])
  267. );
  268. wikichecknotice.append(noticeText);
  269. }
  270. }
  271. if ( noticeExtraParts.length ) wikichecknotice.append(...noticeExtraParts);
  272. }, function(error) {
  273. console.log(error)
  274. } ).finally( function() {
  275. wiki.readOnly = readonly;
  276. wikicheck.disabled = false;
  277. } );
  278. };
  279. }
  280. /** @type {HTMLInputElement} */
  281. const feeds = document.getElementById('wb-settings-feeds');
  282. if ( feeds ) {
  283. /** @type {HTMLDivElement} */
  284. const hidefeeds = document.getElementById('wb-settings-feeds-hide');
  285. /** @type {HTMLInputElement} */
  286. const feedsonly = document.getElementById('wb-settings-feeds-only');
  287. /** @type {HTMLDivElement} */
  288. const hidefeedsonly = document.getElementById('wb-settings-feeds-only-hide');
  289. feeds.addEventListener( 'change', function() {
  290. if ( this.checked ) {
  291. hidefeedsonly.style.visibility = '';
  292. if ( !hidefeeds.style.visibility ) feedsonly.disabled = false;
  293. }
  294. else {
  295. hidefeedsonly.style.visibility = 'hidden';
  296. feedsonly.disabled = true;
  297. }
  298. } );
  299. wiki.addEventListener( 'input', function() {
  300. if ( this.validity.valid && /\.(?:fandom\.com|wikia\.org)$/.test(this.value.split('/')[2]) ) {
  301. hidefeeds.style.visibility = '';
  302. feeds.disabled = false;
  303. if ( !hidefeedsonly.style.visibility ) feedsonly.disabled = false;
  304. }
  305. else {
  306. hidefeeds.style.visibility = 'hidden';
  307. feeds.disabled = true;
  308. feedsonly.disabled = true;
  309. }
  310. } );
  311. }
  312. }
  313. /** @type {HTMLInputElement} */
  314. const avatar = document.getElementById('wb-settings-avatar');
  315. if ( avatar ) {
  316. avatar.addEventListener( 'input', function() {
  317. if ( !/^(?:https?:)?\/\//.test(this.value) ) {
  318. if ( this.validity.valid ) {
  319. this.setCustomValidity(validationMessageInvalidURL);
  320. }
  321. }
  322. else this.setCustomValidity('');
  323. } );
  324. /** @type {HTMLButtonElement} */
  325. const avatarbutton = document.getElementById('wb-settings-avatar-preview');
  326. if ( avatarbutton ) {
  327. const avatarpreview = document.createElement('img');
  328. avatarpreview.id = 'wb-settings-avatar-preview-img';
  329. avatarpreview.classList.add('avatar');
  330. const validContentTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
  331. avatarbutton.onclick = function() {
  332. if ( !avatar.value ) return;
  333. if ( !avatar.validity.valid ) return avatar.reportValidity();
  334. if ( avatar.value === avatar.defaultValue ) {
  335. avatarpreview.src = avatar.value;
  336. avatarbutton.after(avatarpreview);
  337. return;
  338. }
  339. fetch( avatar.value, {
  340. method: 'HEAD',
  341. referrer: ''
  342. } ).catch( function(error) {
  343. if ( avatar.value.startsWith( 'https://cdn.discordapp.com/attachments/' ) && error.name === 'TypeError' ) {
  344. return fetch( avatar.value.replace( 'https://cdn.discordapp.com/attachments/', 'https://media.discordapp.net/attachments/' ), {
  345. method: 'HEAD',
  346. referrer: ''
  347. } );
  348. }
  349. throw error;
  350. } ).then( function(response) {
  351. avatar.value = response.url;
  352. if ( !validContentTypes.includes( response.headers.get('content-type') ) ) {
  353. var invalidContentType = lang('avatar.content_type').replace( /\$1/g, response.headers.get('content-type') );
  354. avatar.setCustomValidity(invalidContentType + '\n' + validContentTypes.join(', ') );
  355. avatar.reportValidity();
  356. return console.log( 'Invalid content type:', response.headers.get('content-type') );
  357. }
  358. avatarpreview.src = avatar.value;
  359. avatarbutton.after(avatarpreview);
  360. }, function(error) {
  361. console.log(error);
  362. avatar.setCustomValidity(lang('avatar.invalid_url'));
  363. avatar.reportValidity();
  364. } );
  365. };
  366. if ( avatar.value ) {
  367. avatarpreview.src = avatar.value;
  368. avatarbutton.after(avatarpreview);
  369. }
  370. }
  371. }
  372. /** @type {HTMLInputElement} */
  373. const logall = document.getElementById('wb-settings-flag_logall');
  374. if ( logall ) {
  375. /** @type {HTMLDivElement} */
  376. const hidelogall = document.getElementById('wb-settings-logall-hide');
  377. /** @type {HTMLSelectElement} */
  378. const logchannel = document.getElementById('wb-settings-channel');
  379. if ( logchannel ) logchannel.addEventListener( 'input', function() {
  380. if ( this.value ) {
  381. hidelogall.style.visibility = '';
  382. logall.disabled = false;
  383. if ( hidelogall.style.display ) hidelogall.style.display = '';
  384. }
  385. else {
  386. hidelogall.style.visibility = 'hidden';
  387. logall.disabled = true;
  388. }
  389. } );
  390. }
  391. /** @type {HTMLInputElement} */
  392. const usergroup = document.getElementById('wb-settings-usergroup');
  393. if ( usergroup ) {
  394. /** @type {HTMLDivElement} */
  395. const multigroup = document.getElementById('wb-settings-usergroup-multiple');
  396. /** @type {HTMLDataListElement} */
  397. const usergrouplist = document.getElementById('wb-settings-usergroup-list');
  398. usergroup.addEventListener( 'input', function() {
  399. if ( /\s*[,|]\s*$/.test(usergroup.value) ) {
  400. var usedGroups = usergroup.value.trim().split(/\s*[,|]\s*/);
  401. var lastChar = usergroup.value.substring(usergroup.value.length - 1);
  402. usergrouplist.childNodes.forEach( function(listedGroup) {
  403. if ( !listedGroup.value ) return;
  404. var lastIndex = listedGroup.value.lastIndexOf(lastChar);
  405. var originalGroup = listedGroup.value.substring(lastIndex + 1).trim();
  406. if ( usedGroups.includes( originalGroup ) ) return;
  407. listedGroup.value = `${usergroup.value.trim()} ${originalGroup}`;
  408. } );
  409. }
  410. var newWidth = usergroup.value.trim().length * 7;
  411. if ( newWidth < usergroup.parentElement.clientWidth * 0.75 ) {
  412. usergroup.style.minWidth = newWidth + 'px';
  413. }
  414. if ( usergroup.value.includes( ',' ) || usergroup.value.includes( '|' ) ) {
  415. multigroup.style.display = '';
  416. multigroup.style.visibility = '';
  417. multigroup.disabled = false;
  418. }
  419. else if ( !multigroup.style.visibility ) {
  420. multigroup.style.visibility = 'hidden';
  421. multigroup.disabled = true;
  422. }
  423. } );
  424. function fillUsergroupList({query: {allmessages: wikiUsergroupList = [], usergroups: wikiUsergroupListPermissions = []} = {}} = {}) {
  425. if ( !wikiUsergroupList.length ) return;
  426. usergrouplist.replaceChildren(...[
  427. ...wikiUsergroupList.filter( wikigroup => {
  428. if ( wikigroup.name === 'group-all' ) return false;
  429. if ( wikigroup.name === 'group-membership-link-with-expiry' ) return false;
  430. if ( wikigroup.name.endsWith( '.css' ) || wikigroup.name.endsWith( '.js' ) ) return false;
  431. if ( wikigroup.name.endsWith( '-member' ) && wikiUsergroupList.some( wikigroupmember => {
  432. return wikigroupmember.name === wikigroup.name.replace( /-member$/, '' );
  433. } ) ) return false;
  434. return true;
  435. } ).map( wikigroup => {
  436. return new Option(wikigroup['*'], wikigroup.name.replace( /^group-/, '' ));
  437. } ),
  438. ...wikiUsergroupListPermissions.map( wikigroup => wikigroup.name ).filter( function(wikigroup) {
  439. if ( wikigroup === '*' ) return false;
  440. if ( wikiUsergroupList.some( wikigroupmember => {
  441. if ( 'group-' + wikigroup === wikigroupmember.name ) return true;
  442. if ( 'group-' + wikigroup + '-member' === wikigroupmember.name ) return true;
  443. return false;
  444. } ) ) return false;
  445. return true;
  446. } ).map( wikigroup => {
  447. return new Option(wikigroup, wikigroup);
  448. } )
  449. ].sort( (a, b) => {
  450. if ( a.value < b.value ) return -1;
  451. if ( a.value > b.value ) return 1;
  452. return 0;
  453. } ));
  454. }
  455. }
  456. /** @type {NodeListOf<HTMLInputElement>} */
  457. const postcount = document.querySelectorAll('.wb-settings-postcount input');
  458. if ( postcount.length ) {
  459. /** @type {HTMLDivElement} */
  460. const postcountinput = document.getElementById('wb-settings-postcount-input');
  461. postcount.forEach( function(radio) {
  462. radio.addEventListener( 'change', function() {
  463. if ( radio.id === 'wb-settings-postcount-both' ) {
  464. postcountinput.style.display = 'none';
  465. }
  466. else {
  467. postcountinput.style.display = '';
  468. }
  469. } );
  470. } );
  471. }
  472. /** @type {HTMLInputElement} */
  473. const prefix = document.getElementById('wb-settings-prefix');
  474. if ( prefix ) prefix.addEventListener( 'input', function() {
  475. if ( prefix.validity.patternMismatch ) {
  476. if ( prefix.value.trim().includes( ' ' ) ) {
  477. prefix.setCustomValidity(lang('prefix.space'));
  478. }
  479. else if ( prefix.value.includes( '`' ) ) {
  480. prefix.setCustomValidity(lang('prefix.code'));
  481. }
  482. else if ( prefix.value.includes( '\\' ) ) {
  483. prefix.setCustomValidity(lang('prefix.backslash'));
  484. }
  485. else prefix.setCustomValidity('');
  486. }
  487. else prefix.setCustomValidity('');
  488. } );
  489. /** @type {HTMLSelectElement} */
  490. const addRole = document.getElementById('wb-settings-addrole');
  491. /** @type {HTMLButtonElement} */
  492. const addRoleButton = document.getElementById('wb-settings-addrole-add');
  493. if ( addRole && addRoleButton ) addRoleButton.onclick = function() {
  494. if ( addRole.value ) {
  495. var selectedRole = addRole.selectedOptions.item(0);
  496. var newPermission = document.createElement('div');
  497. var selectedRoleInfo = selectedRole.textContent.split(' – ');
  498. var newPermissionSpan = document.createElement('span');
  499. newPermissionSpan.textContent = ( selectedRoleInfo[1] || selectedRoleInfo[0] );
  500. newPermissionSpan.title = selectedRoleInfo[0];
  501. var newPermissionDiv0 = document.createElement('div');
  502. newPermissionDiv0.classList.add('wb-settings-permission');
  503. var newPermissionInput = document.createElement('input');
  504. newPermissionInput.type = 'radio';
  505. newPermissionInput.name = 'permission-' + addRole.value;
  506. newPermissionInput.required = true;
  507. newPermissionDiv0.append(newPermissionInput, document.createElement('label'));
  508. /** @type {HTMLDivElement} */
  509. var newPermissionDiv1 = newPermissionDiv0.cloneNode(true);
  510. /** @type {HTMLDivElement} */
  511. var newPermissionDiv2 = newPermissionDiv0.cloneNode(true);
  512. newPermissionDiv0.firstElementChild.id = 'wb-settings-permission-' + addRole.value + '-0';
  513. newPermissionDiv1.firstElementChild.id = 'wb-settings-permission-' + addRole.value + '-1';
  514. newPermissionDiv2.firstElementChild.id = 'wb-settings-permission-' + addRole.value + '-default';
  515. newPermissionDiv0.firstElementChild.value = '0';
  516. newPermissionDiv1.firstElementChild.value = '1';
  517. newPermissionDiv2.firstElementChild.value = '';
  518. newPermissionDiv0.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-0';
  519. newPermissionDiv1.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-1';
  520. newPermissionDiv2.lastElementChild.htmlFor = 'wb-settings-permission-' + addRole.value + '-default';
  521. newPermissionDiv0.lastElementChild.classList.add('wb-settings-permission-deny', 'radio-label');
  522. newPermissionDiv1.lastElementChild.classList.add('wb-settings-permission-allow', 'radio-label');
  523. newPermissionDiv2.lastElementChild.classList.add('wb-settings-permission-default', 'radio-label');
  524. newPermissionDiv0.lastElementChild.textContent = i18nSlashPermission.deny;
  525. newPermissionDiv1.lastElementChild.textContent = i18nSlashPermission.allow;
  526. newPermissionDiv2.lastElementChild.textContent = i18nSlashPermission.default;
  527. newPermissionDiv2.firstElementChild.defaultChecked = true;
  528. newPermission.append(newPermissionSpan, newPermissionDiv0, newPermissionDiv1, newPermissionDiv2);
  529. addRole.parentElement.after(newPermission);
  530. selectedRole.remove();
  531. addRole.firstElementChild.selected = true;
  532. }
  533. };
  534. var textAreas = document.getElementsByTagName('textarea');
  535. if ( textAreas.length ) {
  536. /** @type {HTMLTextAreaElement} */
  537. var textArea = null;
  538. var codeButtons = document.getElementsByClassName('form-button');
  539. for ( var cb = 0; cb < codeButtons.length; cb++ ) {
  540. codeButtons[cb].onclick = addVariable;
  541. }
  542. for ( var ta = 0; ta < textAreas.length; ta++ ) {
  543. updateTextLength.call(textAreas[ta]);
  544. textAreas[ta].addEventListener('keyup', updateTextLength);
  545. textAreas[ta].addEventListener('keydown', allowTabs);
  546. textAreas[ta].onclick = function() {
  547. if ( !textArea ) {
  548. for ( var us = 0; us < codeButtons.length; us++ ) {
  549. codeButtons[us].classList.remove('user-select');
  550. }
  551. }
  552. textArea = this;
  553. };
  554. }
  555. /**
  556. * @this HTMLElement
  557. * @param {MouseEvent} e
  558. */
  559. function addVariable(e) {
  560. if ( !textArea || e.shiftKey ) return;
  561. var start = textArea.selectionStart;
  562. var end = textArea.selectionEnd;
  563. var valueBefore = ( this.dataset?.before || this.innerText );
  564. var valueAfter = ( this.dataset?.after || '' );
  565. if ( (textArea.textLength - (end - start)) + (valueBefore.length + valueAfter.length) > textArea.maxLength ) return document.getSelection().selectAllChildren(this);
  566. if ( valueAfter ) {
  567. textArea.value = textArea.value.substring(0, start) + valueBefore + textArea.value.substring(start, end) + valueAfter + textArea.value.substring(end);
  568. textArea.selectionStart = start + valueBefore.length;
  569. textArea.selectionEnd = end + valueBefore.length;
  570. }
  571. else {
  572. textArea.value = textArea.value.substring(0, start) + valueBefore + textArea.value.substring(end);
  573. textArea.selectionStart = textArea.selectionEnd = start + valueBefore.length;
  574. }
  575. updateTextLength.call(textArea);
  576. textArea.focus();
  577. }
  578. /**
  579. * @this HTMLTextAreaElement
  580. * @param {KeyboardEvent} e
  581. */
  582. function allowTabs(e) {
  583. if ( e.key !== 'Tab' ) return;
  584. if ( this.value.includes( '`ˋ`' ) ) this.value = this.value.replace( /`ˋ`/g, '```' );
  585. var start = this.selectionStart;
  586. var end = this.selectionEnd;
  587. if ( this.value.substring(0, start).includes( '```' ) && this.value.substring(end).includes( '```' ) ) {
  588. e.preventDefault();
  589. if ( this.textLength > this.maxLength ) return;
  590. this.value = this.value.substring(0, start) + '\t' + this.value.substring(end);
  591. this.selectionStart = this.selectionEnd = start + 1;
  592. }
  593. }
  594. /** @this HTMLTextAreaElement */
  595. function updateTextLength() {
  596. this.labels.item(0).children.item(0).textContent = this.textLength + ' / ' + this.maxLength;
  597. }
  598. }
  599. /*
  600. var collapsible = document.getElementsByClassName('collapsible');
  601. for ( var i = 0; i < collapsible.length; i++ ) {
  602. collapsible[i].onclick = function() {
  603. this.classList.toggle('active');
  604. if ( this.id === 'wb-settings-wiki-search' ) {
  605. wiki.toggleAttribute('readonly');
  606. }
  607. var content = this.nextElementSibling;
  608. if ( content.style.display === 'block' ) {
  609. content.style.display = 'none';
  610. }
  611. else {
  612. content.style.display = 'block';
  613. }
  614. };
  615. }
  616. */