Wanikani Burn Manager

Mass Resurrect/Retire of Burn items on WaniKani

  1. // ==UserScript==
  2. // @name Wanikani Burn Manager
  3. // @namespace rfindley
  4. // @description Mass Resurrect/Retire of Burn items on WaniKani
  5. // @version 2.0.5
  6. // @include https://www.wanikani.com/*
  7. // @exclude https://www.wanikani.com/lesson*
  8. // @exclude https://www.wanikani.com/review*
  9. // @copyright 2016+, Robin Findley
  10. // @license MIT; http://opensource.org/licenses/MIT
  11. // @run-at document-end
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. window.burnmgr = {};
  16.  
  17. (function(gobj) {
  18.  
  19. /* globals $, wkof */
  20. /* eslint no-multi-spaces: "off" */
  21.  
  22. //===================================================================
  23. // Initialization of the Wanikani Open Framework.
  24. //-------------------------------------------------------------------
  25. var script_name = 'Burn Manager';
  26. if (!window.wkof) {
  27. if (confirm(script_name+' requires Wanikani Open Framework.\nDo you want to be forwarded to the installation instructions?')) {
  28. window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
  29. }
  30. return;
  31. }
  32.  
  33. wkof.include('ItemData,Menu');
  34. wkof.ready('ItemData,Menu').then(startup);
  35.  
  36. var mgr_added = false, busy = false, items, items_by_id;
  37.  
  38. function startup() {
  39. wkof.Menu.insert_script_link({
  40. name: 'burnmgr',
  41. submenu: 'Open',
  42. title: 'Burn Manager',
  43. on_click: open_burnmgr
  44. });
  45. }
  46.  
  47. function open_burnmgr() {
  48. // Add the manager if not already.
  49. if (!mgr_added) add_mgr();
  50.  
  51. $('#burn_mgr').slideDown();
  52. $('html, body').animate({scrollTop:0},800);
  53. }
  54.  
  55. var srslvls = ['Apprentice 1','Apprentice 2','Apprentice 3','Apprentice 4','Guru 1','Guru 2','Master','Enlightened','Burned'];
  56.  
  57. //-------------------------------------------------------------------
  58. // Display the Burn Manager object.
  59. //-------------------------------------------------------------------
  60. function add_mgr() {
  61. var html =
  62. '<div id="burn_mgr"><div id="burn_mgr_box" class="container">'+
  63. '<h3 class="small-caps invert">Burn Manager <span id="burn_mgr_instr" href="#">[ Instructions ]</span></h3>'+
  64.  
  65. '<form accept-charset="UTF-8" action="#" class="form-horizontal"><fieldset class="additional-info">'+
  66.  
  67. // Instructions
  68. ' <div class="instructions">'+
  69. ' <div class="header small-caps invert">Instructions</div>'+
  70. ' <div class="content">'+
  71. ' <p>Enter your Resurrect/Retire criteria below, then click <span class="btn">Preview</span>.<br>A preview window will open, showing burn items matching the Level and Type criteria.<br>'+
  72. 'You can change your criteria at any time, then click <span class="btn">Preview</span> again to update your settings... but any <b>manually toggled changes will be lost</b>.</p>'+
  73. ' <p class="nogap">In the preview window:</p>'+
  74. ' <ul>'+
  75. ' <li><b>Hover</b> over an item to see <b>item details</b>.</li>'+
  76. ' <li><b>Click</b> an item to <b>toggle</b> its desired state between <b>Resurrect</b> and <b>Retired</b>.</li>'+
  77. ' </ul>'+
  78. ' <p>After you have adjusted all items to their desired state, click <span class="btn">Execute</span> to begin changing you item statuses<br>'+
  79. 'While executing, please allow the progress bar to reach 100% before navigating to another page, otherwise some items will not be Resurrected or Retired.</p>'+
  80. ' <span class="rad">十</span><span class="kan">本</span><span class="voc">本当</span> = Will be Resurrected<br>'+
  81. ' <span class="rad inactive">十</span><span class="kan inactive">本</span><span class="voc inactive">本当</span> = Will be Retired'+
  82. ' </div>'+
  83. ' </div>'+
  84.  
  85. // Settings
  86. ' <div class="control-group">'+
  87. ' <label class="control-label" for="burn_mgr_levels">Level Selection:</label>'+
  88. ' <div class="controls">'+
  89. ' <input id="burn_mgr_levels" type="text" autocomplete="off" class="span6" max_length=255 name="burn_mgr[levels]" placeholder="Levels to resurrect or retire (e.g. &quot;1-3,5&quot;)" value>'+
  90. ' </div>'+
  91. ' </div>'+
  92. ' <div class="control-group">'+
  93. ' <label class="control-label">Item types:</label>'+
  94. ' <div id="burn_mgr_types" class="controls">'+
  95. ' <label class="checkbox inline"><input id="burn_mgr_rad" name="burn_mgr[rad]" type="checkbox" value="1" checked="checked">Radicals</label>'+
  96. ' <label class="checkbox inline"><input id="burn_mgr_kan" name="burn_mgr[kan]" type="checkbox" value="1" checked="checked">Kanji</label>'+
  97. ' <label class="checkbox inline"><input id="burn_mgr_voc" name="burn_mgr[voc]" type="checkbox" value="1" checked="checked">Vocab</label>'+
  98. ' </div>'+
  99. ' </div>'+
  100. ' <div class="control-group">'+
  101. ' <label class="control-label" for="burn_mgr_initial">Action / Initial State:</label>'+
  102. ' <div id="burn_mgr_initial" class="controls">'+
  103. ' <label class="radio inline"><input id="burn_mgr_initial_current" name="burn_mgr[initial]" type="radio" value="0" checked="checked">No change / Current state</label>'+
  104. ' <label class="radio inline"><input id="burn_mgr_initial_resurrect" name="burn_mgr[initial]" type="radio" value="1">Resurrect All</label>'+
  105. ' <label class="radio inline"><input id="burn_mgr_initial_retire" name="burn_mgr[initial]" type="radio" value="2">Retire All</label>'+
  106. ' </div>'+
  107. ' </div>'+
  108. ' <div class="control-group">'+
  109. ' <div id="burn_mgr_btns" class="controls">'+
  110. ' <a id="burn_mgr_preview" href="#burn_mgr_preview" class="btn btn-mini">Preview</a>'+
  111. ' <a id="burn_mgr_execute" href="#burn_mgr_execute" class="btn btn-mini">Execute</a>'+
  112. ' <a id="burn_mgr_close" href="#burn_mgr_close" class="btn btn-mini">Close</a>'+
  113. ' </div>'+
  114. ' </div>'+
  115.  
  116. // Preview
  117. ' <div class="status"><div class="message controls"></div></div>'+
  118. ' <div class="preview"></div>'+
  119. ' <div id="burn_mgr_item_info" class="hidden"></div>'+
  120.  
  121. '</fieldset>'+
  122. '</form>'+
  123. '<hr>'+
  124. '</div></div>';
  125.  
  126. var css =
  127. '#burn_mgr {display:none;}'+
  128.  
  129. '#burn_mgr_instr {margin-left:20px; font-size:0.8em; opacity:0.8; cursor:pointer;}'+
  130. '#burn_mgr .instructions {display:none;}'+
  131. '#burn_mgr .instructions .content {padding:5px;}'+
  132. '#burn_mgr .instructions p {font-size:13px; line-height:17px; margin-bottom:1.2em;}'+
  133. '#burn_mgr .instructions p.nogap {margin-bottom:0;}'+
  134. '#burn_mgr .instructions ul {margin-left:16px; margin-bottom:1.2em;}'+
  135. '#burn_mgr .instructions li {font-size:13px; line-height:17px;}'+
  136. '#burn_mgr .instructions span {cursor:default;}'+
  137. '#burn_mgr .instructions .btn {color:#000; padding:0px 3px 2px 3px;}'+
  138. '#burn_mgr .noselect {-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}'+
  139.  
  140. '#burn_mgr h3 {'+
  141. ' margin-top:10px; margin-bottom:0px; padding:0 30px; border-radius: 5px 5px 0 0;'+
  142. ' background-color: #fbc042;'+
  143. ' background-image: -moz-linear-gradient(-45deg, #fbc550, #faac05);'+
  144. ' background-image: -webkit-linear-gradient(-45deg, #fbc550, #faac05);'+
  145. ' background-image: -o-linear-gradient(-45deg, #fbc550, #faac05);'+
  146. ' background-image: linear-gradient(-45deg, #fbc550, #faac05);'+
  147. '}'+
  148.  
  149. '#burn_mgr form {border-radius:0 0 5px 5px; margin-bottom:10px;}'+
  150. '#burn_mgr #burn_mgr_box fieldset {border-radius:0 0 5px 5px; margin-bottom:0px; padding:10px;}'+
  151. '#burn_mgr .control-group {margin-bottom:10px;}'+
  152. '#burn_mgr .controls .inline {padding-right:10px;}'+
  153. '#burn_mgr .controls .inline input {margin-left:-15px;}'+
  154. '#burn_mgr_btns .btn {width:50px; margin-right:10px;}'+
  155.  
  156. '#burn_mgr .status {display:none;}'+
  157. '#burn_mgr .status .message {display:inline-block; background-color:#ffc; padding:2px 10px; font-weight:bold; border:1px solid #999; min-width:196px;}'+
  158.  
  159. '#burn_mgr .preview {display:none;}'+
  160. '#burn_mgr .header {padding:0px 3px; line-height:1.2em; margin:0px;}'+
  161. '#burn_mgr .preview .header .count {text-transform:none; margin-left:10px;}'+
  162. '#burn_mgr .content {padding:0px 2px 2px 2px; border:1px solid #999; border-top:0px; background-color:#fff; margin-bottom:10px; position:relative;}'+
  163. '#burn_mgr .content span {'+
  164. ' color:#fff;'+
  165. ' font-size:13px;'+
  166. ' line-height:13px;'+
  167. ' margin:0px 1px;'+
  168. ' padding:2px 3px 3px 2px;'+
  169. ' border-radius:4px;'+
  170. ' box-shadow:0 -2px 0 rgba(0,0,0,0.2) inset;'+
  171. ' display:inline-block;'+
  172. '}'+
  173. '#burn_mgr .rad > img {height:0.9em;}'+
  174. '#burn_mgr .rad {background-color:#0096e7; background-image:linear-gradient(to bottom, #0af, #0093dd);}'+
  175. '#burn_mgr .kan {background-color:#ee00a1; background-image:linear-gradient(to bottom, #f0a, #dd0093);}'+
  176. '#burn_mgr .voc {background-color:#9800e8; background-image:linear-gradient(to bottom, #a0f, #9300dd);}'+
  177. '#burn_mgr .rad.inactive {background-color:#c3e3f3; background-image:linear-gradient(to bottom, #d4ebf7, #c3e3f3);}'+
  178. '#burn_mgr .kan.inactive {background-color:#f3c3e3; background-image:linear-gradient(to bottom, #f7d4eb, #f3c3e3);}'+
  179. '#burn_mgr .voc.inactive {background-color:#e3c3f3; background-image:linear-gradient(to bottom, #ebd4f7, #e3c3f3);}'+
  180.  
  181. '#burn_mgr .preview .content span {cursor:pointer;}'+
  182.  
  183. '#burn_mgr_item_info {'+
  184. ' position: absolute;'+
  185. ' padding:8px;'+
  186. ' color: #eeeeee;'+
  187. ' background-color:rgba(0,0,0,0.8);'+
  188. ' border-radius:8px;'+
  189. ' font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;'+
  190. ' font-weight: bold;'+
  191. ' z-index:3;'+
  192. '}'+
  193. '#burn_mgr_item_info .item {font-size:2em; line-height:1.2em;}'+
  194. '#burn_mgr_item_info .item img {height:1em; width:1em; vertical-align:bottom;}'+
  195. '#burn_mgr_item_info>div {padding:0 8px; background-color:#333333;}'+
  196.  
  197. '#burn_mgr hr {border-top-color:#bbb; margin-top:0px; margin-bottom:0px;}';
  198.  
  199. $('head').append('<style type="text/css">'+css+'</style>');
  200. $(html).insertAfter($('.global-header'));
  201.  
  202. // Add event handlers
  203. $('#burn_mgr_preview').on('click', on_preview);
  204. $('#burn_mgr_execute').on('click', on_execute);
  205. $('#burn_mgr_close').on('click', on_close);
  206. $('#burn_mgr_instr').on('click', on_instructions);
  207.  
  208. mgr_added = true;
  209. }
  210.  
  211. //-------------------------------------------------------------------
  212. // Event handler for item click.
  213. //-------------------------------------------------------------------
  214. function item_click_event(e) {
  215. $(e.currentTarget).toggleClass('inactive');
  216. }
  217.  
  218. //-------------------------------------------------------------------
  219. // Event handler for item hover info.
  220. //-------------------------------------------------------------------
  221. function item_info_event(e) {
  222. var hinfo = $('#burn_mgr_item_info');
  223. var target = $(e.currentTarget);
  224. switch (e.type) {
  225. //-----------------------------
  226. case 'mouseenter':
  227. var itype = target.data('type');
  228. var ref = target.data('ref');
  229. var item = items_by_id[ref];
  230. var status = (can_resurrect(item)===true ? 'Retired' : 'Resurrected');
  231. var str = '<div class="'+itype+'">';
  232. var readings, reading_str, important_reading, meanings, meaning_str, synonyms, synonym_str;
  233. switch (itype) {
  234. case 'rad':
  235. meanings = item.data.meanings.filter(primary);
  236. meaning_str = meanings.map(meaning).join(', ');
  237. str += '<span class="item">Item: <span lang="ja">';
  238. if (item.data.characters !== null) {
  239. str += item.data.characters+'</span></span><br />';
  240. } else {
  241. str += '<img src="'+item.data.character_images[0].url+'" /></span></span><br />';
  242. }
  243. str += 'Meaning: '+toTitleCase(meaning_str)+'<br />';
  244. if (item.study_materials && item.study_materials.meaning_synonyms.length > 0) {
  245. str += 'Synonyms: '+toTitleCase(item.study_materials.meaning_synonyms.join(', '))+'<br />';
  246. }
  247. break;
  248. case 'kan':
  249. readings = item.data.readings.filter(primary);
  250. important_reading = readings[0].type;
  251. reading_str = readings.map(reading).join(', ');
  252. meanings = item.data.meanings.filter(primary);
  253. meaning_str = meanings.map(meaning).join(', ');
  254. str += '<span class="item">Item: <span lang="ja">'+item.data.characters+'</span></span><br />';
  255. str += toTitleCase(important_reading)+': <span lang="ja">'+reading_str+'</span><br />';
  256. str += 'Meaning: '+toTitleCase(meaning_str)+'<br />';
  257. if (item.study_materials && item.study_materials.meaning_synonyms.length > 0) {
  258. str += 'Synonyms: '+toTitleCase(item.study_materials.meaning_synonyms.join(', '))+'<br />';
  259. }
  260. break;
  261. case 'voc':
  262. readings = item.data.readings.filter(primary);
  263. reading_str = readings.map(reading).join(', ');
  264. meanings = item.data.meanings.filter(primary);
  265. meaning_str = meanings.map(meaning).join(', ');
  266. str += '<span class="item">Item: <span lang="ja">'+item.data.characters+'</span></span><br />';
  267. str += 'Reading: <span lang="ja">'+reading_str+'</span><br />';
  268. str += 'Meaning: '+toTitleCase(meaning_str)+'<br />';
  269. if (item.study_materials && item.study_materials.meaning_synonyms.length > 0) {
  270. str += 'Synonyms: '+toTitleCase(item.study_materials.meaning_synonyms.join(', '))+'<br />';
  271. }
  272. break;
  273. }
  274. str += 'Level: '+item.data.level+'<br />';
  275. str += 'SRS Level: '+srslvls[item.assignments.srs_stage-1]+'<br />';
  276. str += 'Currently: '+status+'<br />';
  277. str += '</div>';
  278. hinfo.html(str);
  279. hinfo.css('left', target.offset().left - target.position().left);
  280. hinfo.css('top', target.offset().top + target.outerHeight() + 3);
  281. hinfo.removeClass('hidden');
  282. break;
  283.  
  284. //-----------------------------
  285. case 'mouseleave':
  286. hinfo.addClass('hidden');
  287. break;
  288. }
  289. }
  290.  
  291. //-------------------------------------------------------------------
  292. // Filters and maps
  293. //-------------------------------------------------------------------
  294. function primary(info) {return info.primary;}
  295. function meaning(info) {return info.meaning;}
  296. function reading(info) {return info.reading;}
  297.  
  298. //-------------------------------------------------------------------
  299. // Make first letter of each word upper-case.
  300. //-------------------------------------------------------------------
  301. function toTitleCase(str) {
  302. return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
  303. }
  304.  
  305. //-------------------------------------------------------------------
  306. // Read the user's "initial state" setting.
  307. //-------------------------------------------------------------------
  308. function read_initial_state() {
  309. return Number($('#burn_mgr_initial input:checked').val());
  310. }
  311.  
  312. //-------------------------------------------------------------------
  313. // Run when user clicks 'Preview' button
  314. //-------------------------------------------------------------------
  315. function on_preview(e, refresh) {
  316. if (refresh !== true) e.preventDefault();
  317. if (busy) return;
  318.  
  319. var preview_is_open = $('#burn_mgr .preview').is(':visible');
  320. if (preview_is_open) {
  321. $('#burn_mgr .preview').html('').slideUp();
  322. busy = true;
  323. fetch_items(true /* force_update */).then(populate_data.bind(null, refresh));
  324. } else {
  325. busy = true;
  326. fetch_items(true /* force_update */).then(populate_data.bind(null, refresh));
  327. }
  328. }
  329.  
  330. //-------------------------------------------------------------------
  331. // Fetch the requested items
  332. //-------------------------------------------------------------------
  333. function fetch_items(force_update) {
  334. var levels = $('#burn_mgr_levels').val();
  335. if (levels === '') levels = '*';
  336.  
  337. var item_type = [];
  338. if ($('#burn_mgr_rad').attr('checked') === 'checked') item_type.push('rad');
  339. if ($('#burn_mgr_kan').attr('checked') === 'checked') item_type.push('kan');
  340. if ($('#burn_mgr_voc').attr('checked') === 'checked') item_type.push('voc');
  341.  
  342. $('#burn_mgr .status .message').html('Fetching data...');
  343. $('#burn_mgr .status').slideDown();
  344.  
  345. return wkof.ItemData.get_items({
  346. wk_items: {
  347. options: {subjects: true, assignments: true, study_materials: true},
  348. filters: {
  349. have_burned: true,
  350. level: levels,
  351. item_type: item_type
  352. }
  353. }
  354. }, {force_update: force_update});
  355. }
  356.  
  357. //-------------------------------------------------------------------
  358. // Populate the item data on-screen.
  359. //-------------------------------------------------------------------
  360. function populate_data(refresh, data) {
  361. // Hide the "Loading" message.
  362. busy = false;
  363. $('#burn_mgr .status').slideUp();
  364. items = data;
  365. items_by_id = wkof.ItemData.get_index(items, 'subject_id');
  366. window.items = items;
  367.  
  368. var html = '';
  369. var itypes = ['radical', 'kanji', 'vocabulary'];
  370. var state = read_initial_state();
  371. if (refresh === true) state = 0;
  372. var get_initial = [
  373. /* 0 */ function(item) {return can_retire(item);}, // Show current item state.
  374. /* 1 */ function(item) {return true;}, // Mark all items for resurrection.
  375. /* 2 */ function(item) {return false;}, // Mark all items for retirement.
  376. ][state];
  377.  
  378. var items_by_level = wkof.ItemData.get_index(items, 'level');
  379. var item_html, items_by_type, level_items, itype3, list;
  380. for (var level = 1; level <= wkof.user.level; level++) {
  381. level_items = items_by_level[level];
  382. if (!level_items) continue;
  383. items_by_type = wkof.ItemData.get_index(level_items, 'item_type');
  384. item_html = '';
  385. $.each(itypes, populate_by_type);
  386. html +=
  387. '<div class="header small-caps invert">Level '+level+
  388. '</div>'+
  389. '<div class="content level noselect">'+
  390. item_html+
  391. '</div>';
  392. }
  393. function populate_by_type(idx, itype) {
  394. // Skip item types that aren't checked.
  395. itype3 = itype.slice(0,3);
  396. list = items_by_type[itype];
  397. if (!$('#burn_mgr_'+itype3).is(':checked')) return;
  398. if (list === undefined) return;
  399. $.each(list, populate_individual_items);
  400. }
  401. function populate_individual_items(idx,item){
  402. var text, ref, state;
  403. text = item.data.slug;
  404. if (itype3 === 'rad') {
  405. if (item.data.character_images.length > 0) {
  406. text = '<img src="'+item.data.character_images[0].url+'">';
  407. } else {
  408. text = item.data.characters;
  409. }
  410. } else {
  411. text = item.data.characters;
  412. }
  413. if (get_initial(item)) {
  414. state = '';
  415. } else {
  416. state = ' inactive';
  417. }
  418. item_html += '<span class="'+itype3+state+'" data-type="'+itype3+'" data-ref="'+item.id+'">'+text+'</span>';
  419. }
  420. $('#burn_mgr .preview').html(html).slideDown();
  421. $('#burn_mgr .preview .content.level')
  422. .on('mouseenter', 'span', item_info_event)
  423. .on('mouseleave', item_info_event)
  424. .on('click', 'span', item_click_event);
  425. }
  426.  
  427. //-------------------------------------------------------------------
  428. // Run when user clicks 'Execute' button
  429. //-------------------------------------------------------------------
  430. function on_execute(e) {
  431. e.preventDefault();
  432. if (busy) return;
  433. busy = true;
  434.  
  435. var status = $('#burn_mgr .status'), message = $('#burn_mgr .status .message');
  436. var use_preview = $('#burn_mgr .preview').is(':visible');
  437. var task_list = [];
  438.  
  439. var auth_token = encodeURIComponent($('[name="csrf-token"]').attr('content'));
  440.  
  441. if (use_preview) {
  442. $('#burn_mgr .preview .content span').each(function(idx,elem){
  443. elem = $(elem);
  444. var ref = elem.data('ref');
  445. var item = items_by_id[ref];
  446. var current = can_resurrect(item);
  447. var want = elem.hasClass('inactive');
  448. if (current != want) {
  449. task_list.push({url:'/assignments/'+ref+'/'+(want?'burn':'resurrect'),item:item});
  450. }
  451. });
  452. start_execute();
  453. } else {
  454. // Don't use Preview information.
  455. fetch_items(true /* force_update */).then(function(items){
  456. var state = read_initial_state();
  457. if (state === 0) return;
  458. var want = (state===2);
  459. $.each(items, function(idx, item){
  460. var ref = item.id;
  461. var current = can_resurrect(item);
  462. if (current != want) {
  463. task_list.push({url:'/assignments/'+ref+'/'+(want?'burn':'resurrect'),item:item});
  464. }
  465. });
  466. start_execute();
  467. });
  468. }
  469.  
  470. var cnt, tot;
  471.  
  472. function start_execute() {
  473. tot = task_list.length;
  474. cnt = 0;
  475. message.html('Executing 0 / '+tot);
  476. status.slideDown();
  477.  
  478. var simultaneous = Math.min(5, tot);
  479. for (cnt=0; cnt<simultaneous; cnt++) {
  480. retire(task_list[cnt]).then(next, next);
  481. }
  482.  
  483. function next(result) {
  484. if (cnt < tot) {
  485. message.html('Working... ('+cnt+' of '+tot+')');
  486. retire(task_list[cnt++]).then(next, next);
  487. } else {
  488. message.html('Done! ('+cnt+' of '+tot+')');
  489. busy = false;
  490. on_preview(null, true /* refresh */);
  491. }
  492. }
  493. }
  494.  
  495. function retire(task) {
  496. return new Promise(function(resolve, reject){
  497. $.ajax(task.url, {
  498. type:'POST',
  499. data:'_method=put&authenticity_token='+auth_token,
  500. dataType:'text'
  501. }).done(function(){
  502. resolve({status:'success', task:task});
  503. }).fail(function(){
  504. reject({status:'fail', task:task});
  505. });
  506. });
  507. }
  508. }
  509.  
  510. //-------------------------------------------------------------------
  511. // Run when user clicks 'Close' button
  512. //-------------------------------------------------------------------
  513. function on_close(e) {
  514. e.preventDefault();
  515. var preview_is_open = $('#burn_mgr .preview').is(':visible');
  516. if (preview_is_open) $('#burn_mgr .preview').html('').slideUp();
  517. $('#burn_mgr').slideUp();
  518. }
  519.  
  520. //-------------------------------------------------------------------
  521. // Run when user clicks 'Instructions'
  522. //-------------------------------------------------------------------
  523. function on_instructions(e) {
  524. e.preventDefault();
  525. $('#burn_mgr .instructions').slideToggle();
  526. }
  527.  
  528. //-------------------------------------------------------------------
  529. // Return 'true' if item can be retired.
  530. //-------------------------------------------------------------------
  531. function can_retire(item){
  532. return (item.assignments.srs_stage !== 9);
  533. }
  534.  
  535. //-------------------------------------------------------------------
  536. // Return 'true' if item can be resurrected.
  537. //-------------------------------------------------------------------
  538. function can_resurrect(item){
  539. return (item.assignments.srs_stage === 9);
  540. }
  541.  
  542. })(window.burnmgr);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址