You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@community.apache.org by hu...@apache.org on 2019/08/02 13:13:36 UTC

svn commit: r1864224 - in /comdev/reporter.apache.org/trunk/site/wizard: ./ css/ js/ js/source/

Author: humbedooh
Date: Fri Aug  2 13:13:36 2019
New Revision: 1864224

URL: http://svn.apache.org/viewvc?rev=1864224&view=rev
Log:
Lotta updates:
- Scrap old wizard style, stick with unified for every needs.
- Class'ify the stepper and editor for easier maintenance.
- Scrap unneeded css and html.

Added:
    comdev/reporter.apache.org/trunk/site/wizard/js/source/stepper.js
Modified:
    comdev/reporter.apache.org/trunk/site/wizard/css/wizard.css
    comdev/reporter.apache.org/trunk/site/wizard/index.html
    comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js
    comdev/reporter.apache.org/trunk/site/wizard/js/source/generators.js
    comdev/reporter.apache.org/trunk/site/wizard/js/source/init.js
    comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js
    comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js
    comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js
    comdev/reporter.apache.org/trunk/site/wizard/steps.json
    comdev/reporter.apache.org/trunk/site/wizard/unified.html

Modified: comdev/reporter.apache.org/trunk/site/wizard/css/wizard.css
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/css/wizard.css?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/css/wizard.css (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/css/wizard.css Fri Aug  2 13:13:36 2019
@@ -113,18 +113,8 @@
 }
 
 
-#steps {
-    width: 720px;
-    border: 1.5px solid #3339;
-    background: #EEE;
-    border-radius: 10px;
-    margin: 10px auto;
-    padding: 10px;
-    height: 110px;
-    text-align: center;
-}
 
-.unified #steps {
+#unified-steps {
     width: 120px;
     border: 1.5px solid #3339;
     background: #EEE;
@@ -257,10 +247,6 @@
 }
 
 #unified-helper {
- display: none;
-}
-
-.unified #unified-helper {
   display: inline-block;
   width: 400px;
   height: 600px;

Modified: comdev/reporter.apache.org/trunk/site/wizard/index.html
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/index.html?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/index.html (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/index.html Fri Aug  2 13:13:36 2019
@@ -12,12 +12,12 @@
 <meta property='og:image' content='https://reporter.apache.org/guide/logo.png'>
 
 <script type="text/javascript">
-let editor_type = 'default';
+let editor_type = 'unified';
 </script>
 
 <title>ASF Board Report Wizard</title>
-<link rel="stylesheet" href="css/wizard.css"/>
-<link rel="stylesheet" href="highlighter/highlighter.css"/>
+<link rel="stylesheet" href="css/wizard.css?newstuff"/>
+<link rel="stylesheet" href="highlighter/highlighter.css?newstuff"/>
 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
 </head>
 <body>
@@ -26,37 +26,18 @@ let editor_type = 'default';
   <div class="loader"></div>
  </div>
  
-   <div id="wrapper" style="display: none;">
+   <div id="wrapper" style="display: none;" class="unified">
     <h2 id="pname" style="text-align: center;">Board Report Wizard</h2>
-    <div id="steps">
+    <div id="unified-steps">
+     
      
-      
      </div>
     
     <div id="unified-editor">
-     <textarea id="unified-report" onmouseup="find_section();" onkeyup="find_section();"></textarea>
+     <textarea id="unified-report"></textarea>
     </div>
     <div id="unified-helper"></div>
     
-    <div id="wizard-content">
-     <div id="help_wrapper">
-     <h3 id="step_title">Project Activity:</h3>
-     <p id="step_help">
-      
-     </p>
-     </div>
-     <p style="text-align: center; width: 100%; height: 100%;">
-     <textarea id="step_text" style="width: 90%; height: 60%; margin: 0 auto;"
-placeholder="Example activity:
-- This quarter, we attended FooCon - it was a sounding success!
-- Apache Foo 1.2.3 was released on August 4th, 2019."></textarea><br/>
-<button id="step_prev" onclick="build_steps(current_step-1);" class="btn btn-info" style="float: left;">&#x2039;&#x2039; Previous step</button>
-<button id="step_next" onclick="build_steps(current_step+1);" class="btn btn-success" style="float: right;">&#x203A;&#x203A; Next step</button>
-</p>
-    </div>
-    
-    <div id="tips" class="wizard-tip"></div>
-    </div>
    
    <!-- Modal for notices -->
   <div class="modal fade" id="alert" role="dialog">
@@ -91,8 +72,8 @@ placeholder="Example activity:
 <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
 <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
-<script src="highlighter/highlighter.js" type="text/javascript"></script>
-<script src="js/wizard.js" type="text/javascript"></script>
+<script src="highlighter/highlighter.js?newstuff" type="text/javascript"></script>
+<script src="js/wizard.js?newstuff" type="text/javascript"></script>
 </body>
 </html>
 

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/drafts.js Fri Aug  2 13:13:36 2019
@@ -1,14 +1,15 @@
 // Draft saving/loading features
 
 let saved_drafts = null;
+let draft_stepper = null;
 
 function save_draft() {
     js = {
         'project': project,
         'action': 'save',
-        'type': editor_type,
-        'report': JSON.stringify(report),
-        'report_compiled': compile_report(null, true, true)
+        'type': 'unified',
+        'report': JSON.stringify(draft_stepper.editor.report),
+        'report_compiled': draft_stepper.editor.report
     }
     
     let formdata = $.param(js);
@@ -40,14 +41,9 @@ function load_draft(filename) {
 
 function read_draft(state, json) {
     if (json.report) {
-        
-        if (editor_type == 'unified') {
-          document.getElementById('unified-report').value = json.report;
-          window.setTimeout(() => { $('#unified-report').highlightWithinTextarea('update'); }, 250);
-        } else {
-            report = json.report;
-        }
-        build_steps(0, true, true);
+        draft_stepper.editor.object.value = json.report;
+        window.setTimeout(() => { draft_stepper.editor.highlight() }, 250);
+        draft_stepper.build(0, false, false);
         modal("Draft was successfully loaded and is ready.");
     } else {
         modal("Could not load report draft :/");
@@ -55,20 +51,19 @@ function read_draft(state, json) {
 }
 
 
-
-function list_drafts() {
+function list_drafts(pdata, editor) {
   if (!saved_drafts) {
-    GET('drafts.py?action=index&project=%s&type=%s'.format(project, editor_type), show_draft_list, {});
+    GET('drafts.py?action=index&project=%s&type=%s'.format(project, editor_type), show_draft_list, {stepper:editor.stepper});
     return "";
   }
   else {
-    return show_draft_list();
+    return show_draft_list({stepper: editor.stepper});
   }
 }
 
 function show_draft_list(state, json) {
   if (json && json) { saved_drafts = json.drafts || {}; }
-  
+  draft_stepper = state.stepper; // hackish for now!
   let txt = "";
   let filenames = Object.keys(saved_drafts);
   if (filenames.length > 0) {
@@ -87,17 +82,8 @@ function show_draft_list(state, json) {
     txt += "</ul></small>"
   }
   if (json && current_step == 0) {
-    let tip = document.getElementById('tips');
-    if (txt.length > 0) {
-        tip.style.display = 'block';
-        tip.innerHTML = txt;
-    } else {
-        tip.style.display = 'none';
-    }
-    if (editor_type == 'unified') {
-        let tip = document.getElementById('unified-helper');
-        tip.innerHTML += txt;
-    }
+    let tip = document.getElementById('unified-helper');
+    tip.innerHTML += txt;
   } else {
     return txt;
   }

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/generators.js
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/js/source/generators.js?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/generators.js (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/generators.js Fri Aug  2 13:13:36 2019
@@ -14,16 +14,15 @@ let gcd = (x,y) => {
 }
 
 
-function generate_pmc_roster(data) {
-    
+function generate_pmc_roster(pdata) {
     // PMC age
-    let founded = moment(data.pmcdates[project].pmc[2] * 1000.0);
+    let founded = moment(pdata.pmcdates[project].pmc[2] * 1000.0);
     let age = founded.fromNow();
-    let txt = "%s was founded %s (%s)\n".format(data.pmcsummary[project].name, founded.format('YYYY-MM-DD'), age);
+    let txt = "%s was founded %s (%s)\n".format(pdata.pmcsummary[project].name, founded.format('YYYY-MM-DD'), age);
     
     // PMC and committer count
-    let no_com = data.count[project][1];
-    let no_pmc = data.count[project][0];
+    let no_com = pdata.count[project][1];
+    let no_pmc = pdata.count[project][0];
     
     let y1 = no_com;
     let y2 = no_pmc;
@@ -52,7 +51,7 @@ function generate_pmc_roster(data) {
     
     
     // Last PMC addition
-    let changes = data.changes[project].pmc;
+    let changes = pdata.changes[project].pmc;
     let now = moment();
     let three_months_ago = now.subtract(3, 'months');
     let no_added = 0;
@@ -81,7 +80,7 @@ function generate_pmc_roster(data) {
     // Last Committer addition
     txt += "\n"
     txt += "Committership changes, past quarter:\n"
-    changes = data.changes[project].committer;
+    changes = pdata.changes[project].committer;
     now = moment();
     three_months_ago = now.subtract(3, 'months');
     no_added = 0;
@@ -224,97 +223,6 @@ function health_tips(data) {
     return txt;
 }
 
-let compile_okay = false;
-
-function check_compile(data) {
-    
-    compile_okay = true;
-    let text = "";
-    if (editor_type == 'unified') {
-      let required_sections = [];
-      let sections = parse_unified();
-      
-      for (var i = 0; i < step_json.length; i++) {
-        let step = step_json[i];
-        if (!step.noinput) {
-          let found = false;
-          required_sections.push(step.rawname||step.description);
-          for (var n = 0; n < sections.length; n++) {
-            if (sections[n].title == (step.rawname||step.description)) {
-              found = true;
-              if (sections[n].text.indexOf(PLACEHOLDER) != -1) {
-                console.log("Found placeholder text: " + PLACEHOLDER)
-                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: red;'>&#xF7;</span> <kbd>%s</kbd> contains placeholder text!</li>".format(sections[n].title);
-                compile_okay = false;
-              } else if (sections[n].text.length < 20) {
-                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: pink;'>&#8253;</span> <kbd>%s</kbd> seems a tad short?</li>".format(sections[n].title);
-              } else {
-                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: green;'>&#x2713;</span> <kbd>%s</kbd> seems alright</li>".format(sections[n].title);
-                
-              }
-              break;
-            }
-          }
-          if (!found) {
-            compile_okay = false;
-            text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: red;'>&#xF7;</span> <kbd>%s</kbd> is missing from the report!</li>".format(step.description);
-          }
-        }
-        
-      }
-      
-      // Remark on additional sections not required
-      for (var n = 0; n < sections.length; n++) {
-          if (!required_sections.has(sections[n].title)) {
-            text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: pink;'>&#8253;</span> Found unknown section <kbd>%s</kbd></li>".format(sections[n].title);
-          }
-      }
-      
-      
-    }
-    else {
-      for (var i = 1; i < 5; i++) {
-          if (report[i] == null || report[i].length == 0) {
-              text += "<li>You have not filled out the <kbd>%s</kbd> section yet.".format(step_json[i].description);
-              compile_okay = false;
-          }
-      }
-    }
-    
-    if (text.length > 0) {
-        text = "<h5>Report review results:</h5>The following remarks were logged by the report compiler:<br/><ul>" + text + "</ul>";
-    }
-    if (!compile_okay) {
-      text += "Your report could possibly use some more work, and that's okay! You can always save your current report as a draft and return later to work more on it. Drafts are saved for up to two months.";
-    }
-    else {
-        text += "That's it, your board report compiled a-okay and is potentially ready for submission! If you'd like more time to work on it, you can save it as a draft, and return later to make some final edits. Or you can publish it to the agenda via Whimsy.";
-    }
-    text += "<br/><button class='btn btn-warning' onclick='save_draft();'>Save as draft</button>"
-    if (compile_okay) text += " &nbsp; &nbsp; <button class='btn btn-success'>Publish via Whimsy</button>"
-    return text;
-}
-
-
-function compile_report(data, okay, force) {
-    if (!okay && !force) return -1
-    if (editor_type == 'unified') {
-      return document.getElementById('unified-report').value;
-    }
-    let rep = "## Board Report for %s ##\n".format(pdata.pdata[project].name);
-    for (var i = 1; i < 5; i++) {
-        let step = step_json[i];
-        rep += "\n## %s:\n".format(step.description);
-        if (report[i] !== null) {
-            rep += report[i].replace(/(\r?\n)+$/, '');
-        } else {
-            rep += "Nothing entered yet...\n";
-        }
-        rep += "\n";
-    }
-    return rep;
-}
-
 function activity_tips(data) {
     let three_months_ago = moment().subtract(3, 'months');
     let txt = "";
@@ -360,9 +268,13 @@ function reflow(txt, chars) {
   return output;
 }
 
-function get_charter(data) {
-  let charter = data.pdata[project].charter;
+function get_charter(pdata) {
+  let charter = pdata.pdata[project].charter;
   
   let txt = reflow(charter);
   return txt;
+}
+
+function compile_check(pdata, editor) {
+  return editor.compile();
 }
\ No newline at end of file

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/init.js
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/js/source/init.js?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/init.js (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/init.js Fri Aug  2 13:13:36 2019
@@ -21,6 +21,9 @@ let project = location.search.substr(1);
 let loaded_from_draft = false;
 let PLACEHOLDER = '[Insert your own data here]';
 
+let editor = null;
+let stepper = null;
+
 if (project.length < 2) {
     GET("/reportingcycles.json", pre_splash, {});
 } else {

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/primer.js Fri Aug  2 13:13:36 2019
@@ -1,8 +1,6 @@
+// some glopbal vars for now - we'll get them localized soon enough.
 let pdata = {};
-let report = [null,null,null,null,null,null];
-let current_step = 0;
 let cycles = {};
-let draft_mode = false;
 let comments = {};
 
 function modal(txt) {
@@ -39,151 +37,16 @@ function prime_cycles(state, json) {
 }
 
 
-let step_json = {};
 function prime_steps(state, json) {
+    // Cancel spinner
     document.getElementById('wizard_spinner').style.display = 'none';
     document.getElementById('wrapper').style.display = 'block';
-    step_json = json.steps;
-    build_steps(0, true);
-}
-
-let hilite_timer = null;
-
-function build_steps(s, start, noclick, e) {
-    s = s || 0;
-    
-    let text = document.getElementById('step_text');
-    if (!start && text && text.value.length > 0 && current_step < 5) {
-        report[current_step] = text.value;
-    }
-   
-    text.innerText = '';
-    let step_changed = (s == current_step) ? false : true;
-    current_step = s;
     
-    let stepParent = document.getElementById('steps');
-    stepParent.innerHTML = '';
-    for (var i = 0; i < step_json.length; i++) {
-        let element = step_json[i];
-        let wrapper = new HTML('div', {class: 'wizard-step-wrapper', onclick: 'build_steps(%u);'.format(i)});
-        let stepcircle = new HTML('div', {class: 'wizard-step'});
-        let stepicon = new HTML('i', {class: 'fas fa-%s'.format(element.icon)});
-        stepcircle.inject(stepicon);
-        let steptext = new HTML('div', {class: 'wizard-step-text'}, element.description);
-        wrapper.inject([stepcircle, steptext]);
-        if (s == i) {
-            stepcircle.setAttribute('class', 'wizard-step active');
-            steptext.setAttribute('class', 'wizard-step-text active');
-        }
-        if (i < s) {
-            stepcircle.setAttribute('class', 'wizard-step done');
-            steptext.setAttribute('class', 'wizard-step-text done');
-        }
-        stepParent.inject(wrapper);
-        if (i < step_json.length-1) {
-            let line = new HTML('div', {class: 'wizard-line'});
-            if (i < s) line.setAttribute('class', 'wizard-line done');
-            stepParent.inject(line);
-        }
-        if (s == i) {
-            let title = document.getElementById('step_title');
-            title.innerText = element.description;
-            let help = document.getElementById('step_help');
-            help.innerHTML = element.help || "No helper information available for this step...";
-            if (element.helpgenerator) {
-                let data = eval("%s(pdata);".format(element.helpgenerator));
-                help.innerHTML = data;
-            }
-            let text = document.getElementById('step_text');
-            text.placeholder = element.placeholder || "";
-            let hw = document.getElementById('help_wrapper');
-            if (element.noinput) {
-                text.style.display = 'none';
-            }
-            else text.style.display = 'inline';
-            text.style.height = (395 - hw.scrollHeight) + "px";
-            if (element.generator && !(report[s] && report[s].length > 0)) {
-                let data = eval("%s(pdata, compile_okay);".format(element.generator));
-                if (data === -1) text.style.display = 'none'; // hide if generator return -1
-                else text.value = data;
-            }
-            else if (report[s]) {
-                text.value = report[s];
-            } else {
-                text.value = '';
-            }
-            
-            // tips??
-            let tip = document.getElementById('tips');
-            if (element.tipgenerator) {
-                let data = eval("%s(pdata);".format(element.tipgenerator));
-                if (data && data.length > 0) {
-                    tip.innerHTML = data;
-                    tip.style.display = 'block';
-                } else {
-                    tip.style.display = 'none';
-                }
-            } else {
-                tip.style.display = 'none';
-            }
-        }
-    }
-    
-    let bp = document.getElementById('step_prev');
-    if (s == 0) bp.style.display = 'none';
-    else bp.style.display = 'block';
-    
-    let bn = document.getElementById('step_next');
-    if (s == step_json.length -1) bn.style.display = 'none';
-    else bn.style.display = 'block';
-    
-    if (editor_type == 'unified') {
-        if (start) {
-            let template = "";
-            for (var i = 0; i < step_json.length; i++) {
-                let step = step_json[i];
-                if (!step.noinput || step.rawname) {
-                    template += "## %s:\n".format(step.rawname || step.description);
-                    if (step.generator) {
-                        let data = eval("%s(pdata);".format(step.generator));
-                        if (data && data.length > 0) template += data
-                    } else {
-                        template += PLACEHOLDER;
-                    }
-                    template += "\n\n";
-                }
-            }
-            document.getElementById('unified-report').value = template;
-        }
-        if (report_changed) hilite_sections();
-        
-        let step = step_json[s];
-        let helper = document.getElementById('unified-helper');
-        
-        helper.innerHTML = "<h5>%s:</h5>".format(step.description);
-        // Add in help
-        if (step.helpgenerator) {
-            let data = eval("%s(pdata);".format(step.helpgenerator));
-            helper.innerHTML += data;
-        } else if (step.help) {
-            helper.innerHTML += step.help;
-        }
-        
-        // Add tips?
-        if (step.tipgenerator) {
-            let data = eval("%s(pdata);".format(step.tipgenerator));
-            helper.innerHTML += data;
-        }
-        // If clicked to a section, move cursor
-        if (!noclick) {
-            set_position(step.description);
-        }
-        if (step_changed || !noclick)  mark_section(step.rawname||step.description);
-        else {
-            window.clearTimeout(hilite_timer);
-            if (event && event.keyCode == 13) mark_section(step.rawname||step.description);
-            else hilite_timer = window.setTimeout(() => { mark_section(step.rawname||step.description)}, 200);
-        }
-    }
+    // Create editor and stepper class
+    let editor = new UnifiedEditor('unified-report', json.steps);
+    let stepper = new ReportStepper('unified-steps', editor, json.steps, 'unified-helper');
+    editor.stepper = stepper;
+    stepper.pdata = pdata;
+    stepper.build(0, true);    
 }
 

Added: comdev/reporter.apache.org/trunk/site/wizard/js/source/stepper.js
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/js/source/stepper.js?rev=1864224&view=auto
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/stepper.js (added)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/stepper.js Fri Aug  2 13:13:36 2019
@@ -0,0 +1,91 @@
+
+// Left-hand side stepper for reports
+function ReportStepper(div, editor, layout, helper) {
+    
+    // bind to object
+    if (typeof div == "string") this.object = document.getElementById(div);
+    else this.object = div;
+    
+    // bind to helper
+    if (typeof helper == "string") this.helper = document.getElementById(helper);
+    else this.helper = helper;
+    
+    this.layout = layout;
+    this.editor = editor;
+    this.timer = null; // highlight timer
+    this.step = -1;
+    this.changed = false;
+    this.pdata = null;
+    
+    this.build =function (s, start, noclick, e) {
+        s = s || 0;
+        
+        this.changed = (s == this.step) ? false : true;
+        this.step = s;
+        
+        if (start) {
+            this.editor.reset();
+        }
+        
+        
+        if (this.changed) this.editor.highlight();
+        
+        // build the step div
+        this.object.innerHTML = '';
+        for (var i = 0; i < this.layout.length; i++) {
+            let element = this.layout[i];
+            let wrapper = new HTML('div', {class: 'wizard-step-wrapper'});
+            let x = i;
+            wrapper.addEventListener('click', () => { this.build(x);});
+            let stepcircle = new HTML('div', {class: 'wizard-step'});
+            let stepicon = new HTML('i', {class: 'fas fa-%s'.format(element.icon)});
+            stepcircle.inject(stepicon);
+            let steptext = new HTML('div', {class: 'wizard-step-text'}, element.description);
+            wrapper.inject([stepcircle, steptext]);
+            if (s == i) {
+                stepcircle.setAttribute('class', 'wizard-step active');
+                steptext.setAttribute('class', 'wizard-step-text active');
+            }
+            if (i < s) {
+                stepcircle.setAttribute('class', 'wizard-step done');
+                steptext.setAttribute('class', 'wizard-step-text done');
+            }
+            this.object.inject(wrapper);
+            if (i < step_json.length-1) {
+                let line = new HTML('div', {class: 'wizard-line'});
+                if (i < s) line.setAttribute('class', 'wizard-line done');
+                this.object.inject(line);
+            }
+        }
+            
+        let step = this.layout[s];
+        // If helper exists, show useful data
+        if (this.helper) {
+            this.helper.innerHTML = "<h5>%s:</h5>".format(step.description);
+            // Add in help
+            if (step.helpgenerator) {
+                let f = Function('a', 'b', "return %s(a, b);".format(step.helpgenerator));
+                data = f(this.pdata, this.editor)
+                this.helper.innerHTML += data;
+            } else if (step.help) {
+                this.helper.innerHTML += step.help;
+            }
+            // Add tips?
+            if (step.tipgenerator) {
+                let f = Function('a', 'b', "return %s(a,b);".format(step.tipgenerator));
+                data = f(this.pdata, this.editor)
+                this.helper.innerHTML += data;
+            }
+            // If clicked to a section, move cursor
+            if (!noclick) {
+                this.editor.set_position(step.description);
+            }
+            if (this.changed || !noclick)  this.editor.mark_section(step.rawname||step.description);
+            else {
+                window.clearTimeout(this.timer);
+                if (event && event.keyCode == 13) this.editor.mark_section(step.rawname||step.description);
+                else this.timer = window.setTimeout(() => { this.editor.mark_section(step.rawname||step.description)}, 200);
+            }
+        }
+    }
+}
\ No newline at end of file

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/unified.js Fri Aug  2 13:13:36 2019
@@ -1,130 +1,254 @@
 
-function hilite_sections(b) {
-    if (highlighted && !b) return;
-    highlighted = true;
-    let hilites = [];
-    
+// Function that highlights headers and optional sectin in a unified editor
+function UnifiedEditor_highlight_sections(additional_text) {
+    // Don't highlight unless we haven't before or new text is noted
+    if (this.have_highligted && !additional_text) return;
     
+    // Set which sections  highlight
+    let hilites = [];
+      // Headers are blue
     hilites.push({highlight: /^## [^\r\n]+:/mg, className: 'blue' });
+      // Placeholders are grey with red border
     hilites.push({highlight: PLACEHOLDER, className: 'none' });
     
+    // Capture text cursor position(s) before we continue.
     let x = $('#unified-report').selectionStart;
     let y = $('#unified-report').selectionEnd;
-        
-    if (b) {
+    
+    // If additional text is marked for highlighting, we'll have to
+    // first destroy any original highlighting, as it's params changed!
+    if (additional_text) {
         $('#unified-report').highlightWithinTextarea('destroy');
+        
+        // Sections are marked light green
         hilites.push({
-            highlight: b,
+            highlight: additional_text,
             className: 'green'
             });
     }
     
-    
-    $('#unified-report').highlightWithinTextarea({
+    // Run the highlighter on ourselves
+    $(this.object).highlightWithinTextarea({
             highlight: hilites
         });
+    
+    // If x == y (cursor is present and not marking characters),
+    // We'll force focus on ourselves as highlighting loses it.
     if (x == y) {
-        $('#unified-report').selectionStart = x;
-        $('#unified-report').selectionEnd = y;
-        $('#unified-report').focus();
+        $(this.object).selectionStart = x;
+        $(this.object).selectionEnd = y;
+        $(this.object).focus();
     }
 }
 
 
-let report_unified = "";
-let report_changed = true;
-let highlighted = false;
 
-function find_section(e) {
-    let tmp = document.getElementById('unified-report').value;
-    report_changed = (report_unified == tmp) ? false : true;
-    report_unified = tmp;
-    let spos = $('#unified-report').prop("selectionStart");
-    let helper = document.getElementById('unified-helper');
+// Function for figuring out WHERE we are in our report, cursor-wise
+function UnifiedEditor_find_section(e) {
+    let tmp = this.object.value;
+    this.changed = (this.report == tmp) ? false : true;
+    this.report = tmp;
+    let spos = this.object.selectionStart;
     
     // Hop to next newline, so marking the title will jump to the right section
-    while (report_unified[spos] != "\n" && spos < report_unified.length) spos++;
+    while (this.report[spos] != "\n" && spos < this.report.length) spos++;
     
-    let tprec = report_unified.substr(0, spos);
+    let tprec = this.report.substr(0, spos);
     let at_step = -1;
-    for (var i = 0; i < step_json.length; i++) {
-        let step = step_json[i];
+    for (var i = 0; i < this.layout.length; i++) {
+        let step = this.layout[i];
         let tline = "## %s:".format(step.rawname || step.description);
         if (tprec.indexOf(tline) != -1) {
             at_step = i;
         }
     }
     
-    if (at_step != -1) {
-        build_steps(at_step, false, true, e);
-        
-    } else {
-        helper.innerText = "";
-    }
+    if (at_step != -1 && this.stepper) {
+        this.stepper.build(at_step, false, true, e);
+    } 
 }
 
-function set_position(text) {
-    let editor = document.getElementById('unified-report');
-    let pos = editor.value.search(text);
-    if (pos && pos > 0) {
-        editor.selectionStart = (pos + text.length + 2);
-        editor.selectionEnd = (pos + text.length + 2);
-        editor.focus();
+
+// Quick shortcut to focusing somewhere in the report
+function UnifiedEditor_set_position(text) {
+    let pos = this.object.value.indexOf(text);
+    if (pos != -1) {
+        this.object.selectionStart = (pos + text.length + 2);
+        this.object.selectionEnd = (pos + text.length + 2);
+        this.object.focus();
     }
 }
 
 // Parses a unified report into sections
-function parse_unified(quiet) {
-    let sections = [];
-    let sX = 0;
-    let tmp = document.getElementById('unified-report').value;
+function UnifiedEditor_parse_report(quiet) {
+    this.sections = []; // Reset sections
+    let sX = 0; // sX is our moving cursor in the text as we parse.
+    let tmp = this.object.value; // get our textarea data
     while (tmp.length > 0) {
+      // Look for the next "## Foo Bar:" line
       let nextheader = tmp.match(/^## ([^\r\n]+)\r?\n/m);
       if (nextheader) {
-        if (!quiet) console.log("Found report header: %s".format(nextheader[0]))
+        if (!quiet) console.log("Found report header: %s".format(nextheader[0]));
         let title = nextheader[1];
         let spos = tmp.indexOf(nextheader[0]);
         if (spos != -1) {
-          sX += spos + nextheader[0].length;
+          sX += spos + nextheader[0].length; // move cursor
           sY = sX;
+          // ourr buffer past this header, find another one further down
           tmp = tmp.substr(spos + nextheader[0].length);
           let epos = tmp.search(/^## [^\r\n]+/m);
+          // if no further headers, use end of buffer as end pos.
           epos = (epos == -1) ? tmp.length : epos;
           let section = tmp.substr(0, epos);
+          // We got something(?), push to sections array.
           if (title.length > 2) {
-            sections.push({
-              title: title.replace(/:.*$/, ''),
+            this.sections.push({
+              title: title.replace(/:.*$/, ''), // crop away colon and any spaces following
               text: section,
               start: sX,
               end: sX + epos
             });
           }
           if (!quiet) console.log("Section contains:");
-          if (!quiet) console.log(section)
+          if (!quiet) console.log(section);
           tmp = tmp.substr(epos);
-        } else { break }
+        } else { break; }
       } else {
         if (!quiet) console.log("No more report headers found.");
       }
       
     }
-    return sections;
 }
 
 
-// Mark a section using the highlighter
-function mark_section(title) {
-    let sections = parse_unified(true);
+// Mark a section using the highlighter.
+function UnifiedEditor_mark_section(title) {
+    this.parse(true);
     let foundit = false;
-    for (var i = 0; i < sections.length; i++) {
-        if (sections[i].title == title && sections[i].text.indexOf(PLACEHOLDER) == -1 && sections[i].text.length > 4) {
+    for (var i = 0; i < this.sections.length; i++) {
+        let section = this.sections[i];
+        if (section.title == title && section.text.indexOf(PLACEHOLDER) == -1 && section.text.length > 4) {
             //console.log("Marking entire %s section from %u to %u".format(title, sections[i].start, sections[i].end))
-            hilite_sections(sections[i].text);
+            this.highlight(section.text);
             foundit = true;
-            break
+            break;
         }
     }
+    // If we don't know what to highlight, "reset" by highlighting a
+    // piece of text that doesn't exist. HACK HACK HACK.
     if (!foundit) {
-        hilite_sections("<-- EXTERMINATE -->");
+        this.highlight("<-- EXTERMINATE -->");
     }
-}
\ No newline at end of file
+}
+
+
+// Function for resetting a report to follow layout
+function UnifiedEditor_reset() {
+    this.report = "";
+    this.changed = true;
+    for (var i = 0; i < this.layout.length; i++) {
+        let step = this.layout[i];
+        if (!step.noinput || step.rawname) {
+            this.report += "## %s:\n".format(step.rawname || step.description);
+            if (step.generator) {
+                let f = Function('a', "return %s(a);".format(step.generator));
+                data = f(this.stepper.pdata)
+                if (data && data.length > 0) this.report += data
+            } else {
+                this.report += PLACEHOLDER;
+            }
+            this.report += "\n\n";
+        }
+    }
+    this.object.value = this.report;
+    this.sections = [];
+}
+
+// Function for compiling (validating) a report
+function UnifiedEditor_compile() {
+    this.compiles = true;
+    let text = "";
+      let required_sections = [];
+      this.parse();
+      for (var i = 0; i < this.layout.length; i++) {
+        let step = this.layout[i];
+        if (!step.noinput) {
+          let found = false;
+          required_sections.push(step.rawname||step.description);
+          for (var n = 0; n < this.sections.length; n++) {
+            if (this.sections[n].title == (step.rawname||step.description)) {
+              found = true;
+              if (this.sections[n].text.indexOf(PLACEHOLDER) != -1) {
+                console.log("Found placeholder text: " + PLACEHOLDER)
+                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: red;'>&#xF7;</span> <kbd>%s</kbd> contains placeholder text!</li>".format(this.sections[n].title);
+                this.compiles = false;
+              } else if (this.sections[n].text.length < 20) {
+                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: pink;'>&#8253;</span> <kbd>%s</kbd> seems a tad short?</li>".format(this.sections[n].title);
+              } else {
+                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: green;'>&#x2713;</span> <kbd>%s</kbd> seems alright</li>".format(this.sections[n].title);
+                
+              }
+              break;
+            }
+          }
+          if (!found) {
+            this.compiles = false;
+            text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: red;'>&#xF7;</span> <kbd>%s</kbd> is missing from the report!</li>".format(step.description);
+          }
+        }
+        
+      }
+      
+      // Remark on additional sections not required
+      for (var n = 0; n < this.sections.length; n++) {
+          if (!required_sections.has(this.sections[n].title)) {
+            text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: pink;'>&#8253;</span> Found unknown section <kbd>%s</kbd></li>".format(this.sections[n].title);
+          }
+      }
+     
+    
+    if (text.length > 0) {
+        text = "<h5>Report review results:</h5>The following remarks were logged by the report compiler:<br/><ul>" + text + "</ul>";
+    }
+    if (!this.compiles) {
+      text += "Your report could possibly use some more work, and that's okay! You can always save your current report as a draft and return later to work more on it. Drafts are saved for up to two months.";
+    }
+    else {
+        text += "That's it, your board report compiled a-okay and is potentially ready for submission! If you'd like more time to work on it, you can save it as a draft, and return later to make some final edits. Or you can publish it to the agenda via Whimsy.";
+    }
+    text += "<br/><button class='btn btn-warning' onclick='save_draft();'>Save as draft</button>"
+    if (this.compiles) text += " &nbsp; &nbsp; <button class='btn btn-success'>Publish via Whimsy</button>"
+    return text;
+}
+
+
+// This is the Unfied Editor for reports.
+function UnifiedEditor(div, layout) {
+    // Bind to our textarea, direct or via ID.
+    console.log(typeof div)
+    if (typeof div == "string") this.object = document.getElementById(div);
+    else this.object = div;
+    
+    this.layout = layout; // our JSON report layout (steps.json)
+    this.sections = []; // Sections we have or have found in the editor.
+    this.report = ""; // The combined report
+    this.stepper = null; // optional stepper class
+    
+    this.have_highligted = false; // whether we have highlighted before?
+    this.changed = false;  // whether report changed since last parse
+    this.compiles = false; // Whether compiles are okay
+    
+    // Function references
+    this.highlight = UnifiedEditor_highlight_sections;
+    this.mark_section = UnifiedEditor_mark_section;
+    this.parse = UnifiedEditor_parse_report;
+    this.set_position = UnifiedEditor_set_position;
+    this.reset = UnifiedEditor_reset;
+    this.find_section = UnifiedEditor_find_section;
+    this.compile = UnifiedEditor_compile;
+    
+    // set div events
+    this.object.addEventListener('keyup', () => { this.find_section(true); });
+    this.object.addEventListener('mouseup', () => { this.find_section(); });
+    
+}

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js Fri Aug  2 13:13:36 2019
@@ -1016,14 +1016,15 @@ function showCalendarPicker(parent, seed
 // Draft saving/loading features
 
 let saved_drafts = null;
+let draft_stepper = null;
 
 function save_draft() {
     js = {
         'project': project,
         'action': 'save',
-        'type': editor_type,
-        'report': JSON.stringify(report),
-        'report_compiled': compile_report(null, true, true)
+        'type': 'unified',
+        'report': JSON.stringify(draft_stepper.editor.report),
+        'report_compiled': draft_stepper.editor.report
     }
     
     let formdata = $.param(js);
@@ -1055,14 +1056,9 @@ function load_draft(filename) {
 
 function read_draft(state, json) {
     if (json.report) {
-        
-        if (editor_type == 'unified') {
-          document.getElementById('unified-report').value = json.report;
-          window.setTimeout(() => { $('#unified-report').highlightWithinTextarea('update'); }, 250);
-        } else {
-            report = json.report;
-        }
-        build_steps(0, true, true);
+        draft_stepper.editor.object.value = json.report;
+        window.setTimeout(() => { draft_stepper.editor.highlight() }, 250);
+        draft_stepper.build(0, false, false);
         modal("Draft was successfully loaded and is ready.");
     } else {
         modal("Could not load report draft :/");
@@ -1070,20 +1066,19 @@ function read_draft(state, json) {
 }
 
 
-
-function list_drafts() {
+function list_drafts(pdata, editor) {
   if (!saved_drafts) {
-    GET('drafts.py?action=index&project=%s&type=%s'.format(project, editor_type), show_draft_list, {});
+    GET('drafts.py?action=index&project=%s&type=%s'.format(project, editor_type), show_draft_list, {stepper:editor.stepper});
     return "";
   }
   else {
-    return show_draft_list();
+    return show_draft_list({stepper: editor.stepper});
   }
 }
 
 function show_draft_list(state, json) {
   if (json && json) { saved_drafts = json.drafts || {}; }
-  
+  draft_stepper = state.stepper; // hackish for now!
   let txt = "";
   let filenames = Object.keys(saved_drafts);
   if (filenames.length > 0) {
@@ -1102,17 +1097,8 @@ function show_draft_list(state, json) {
     txt += "</ul></small>"
   }
   if (json && current_step == 0) {
-    let tip = document.getElementById('tips');
-    if (txt.length > 0) {
-        tip.style.display = 'block';
-        tip.innerHTML = txt;
-    } else {
-        tip.style.display = 'none';
-    }
-    if (editor_type == 'unified') {
-        let tip = document.getElementById('unified-helper');
-        tip.innerHTML += txt;
-    }
+    let tip = document.getElementById('unified-helper');
+    tip.innerHTML += txt;
   } else {
     return txt;
   }
@@ -1163,16 +1149,15 @@ let gcd = (x,y) => {
 }
 
 
-function generate_pmc_roster(data) {
-    
+function generate_pmc_roster(pdata) {
     // PMC age
-    let founded = moment(data.pmcdates[project].pmc[2] * 1000.0);
+    let founded = moment(pdata.pmcdates[project].pmc[2] * 1000.0);
     let age = founded.fromNow();
-    let txt = "%s was founded %s (%s)\n".format(data.pmcsummary[project].name, founded.format('YYYY-MM-DD'), age);
+    let txt = "%s was founded %s (%s)\n".format(pdata.pmcsummary[project].name, founded.format('YYYY-MM-DD'), age);
     
     // PMC and committer count
-    let no_com = data.count[project][1];
-    let no_pmc = data.count[project][0];
+    let no_com = pdata.count[project][1];
+    let no_pmc = pdata.count[project][0];
     
     let y1 = no_com;
     let y2 = no_pmc;
@@ -1201,7 +1186,7 @@ function generate_pmc_roster(data) {
     
     
     // Last PMC addition
-    let changes = data.changes[project].pmc;
+    let changes = pdata.changes[project].pmc;
     let now = moment();
     let three_months_ago = now.subtract(3, 'months');
     let no_added = 0;
@@ -1230,7 +1215,7 @@ function generate_pmc_roster(data) {
     // Last Committer addition
     txt += "\n"
     txt += "Committership changes, past quarter:\n"
-    changes = data.changes[project].committer;
+    changes = pdata.changes[project].committer;
     now = moment();
     three_months_ago = now.subtract(3, 'months');
     no_added = 0;
@@ -1373,97 +1358,6 @@ function health_tips(data) {
     return txt;
 }
 
-let compile_okay = false;
-
-function check_compile(data) {
-    
-    compile_okay = true;
-    let text = "";
-    if (editor_type == 'unified') {
-      let required_sections = [];
-      let sections = parse_unified();
-      
-      for (var i = 0; i < step_json.length; i++) {
-        let step = step_json[i];
-        if (!step.noinput) {
-          let found = false;
-          required_sections.push(step.rawname||step.description);
-          for (var n = 0; n < sections.length; n++) {
-            if (sections[n].title == (step.rawname||step.description)) {
-              found = true;
-              if (sections[n].text.indexOf(PLACEHOLDER) != -1) {
-                console.log("Found placeholder text: " + PLACEHOLDER)
-                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: red;'>&#xF7;</span> <kbd>%s</kbd> contains placeholder text!</li>".format(sections[n].title);
-                compile_okay = false;
-              } else if (sections[n].text.length < 20) {
-                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: pink;'>&#8253;</span> <kbd>%s</kbd> seems a tad short?</li>".format(sections[n].title);
-              } else {
-                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: green;'>&#x2713;</span> <kbd>%s</kbd> seems alright</li>".format(sections[n].title);
-                
-              }
-              break;
-            }
-          }
-          if (!found) {
-            compile_okay = false;
-            text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: red;'>&#xF7;</span> <kbd>%s</kbd> is missing from the report!</li>".format(step.description);
-          }
-        }
-        
-      }
-      
-      // Remark on additional sections not required
-      for (var n = 0; n < sections.length; n++) {
-          if (!required_sections.has(sections[n].title)) {
-            text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: pink;'>&#8253;</span> Found unknown section <kbd>%s</kbd></li>".format(sections[n].title);
-          }
-      }
-      
-      
-    }
-    else {
-      for (var i = 1; i < 5; i++) {
-          if (report[i] == null || report[i].length == 0) {
-              text += "<li>You have not filled out the <kbd>%s</kbd> section yet.".format(step_json[i].description);
-              compile_okay = false;
-          }
-      }
-    }
-    
-    if (text.length > 0) {
-        text = "<h5>Report review results:</h5>The following remarks were logged by the report compiler:<br/><ul>" + text + "</ul>";
-    }
-    if (!compile_okay) {
-      text += "Your report could possibly use some more work, and that's okay! You can always save your current report as a draft and return later to work more on it. Drafts are saved for up to two months.";
-    }
-    else {
-        text += "That's it, your board report compiled a-okay and is potentially ready for submission! If you'd like more time to work on it, you can save it as a draft, and return later to make some final edits. Or you can publish it to the agenda via Whimsy.";
-    }
-    text += "<br/><button class='btn btn-warning' onclick='save_draft();'>Save as draft</button>"
-    if (compile_okay) text += " &nbsp; &nbsp; <button class='btn btn-success'>Publish via Whimsy</button>"
-    return text;
-}
-
-
-function compile_report(data, okay, force) {
-    if (!okay && !force) return -1
-    if (editor_type == 'unified') {
-      return document.getElementById('unified-report').value;
-    }
-    let rep = "## Board Report for %s ##\n".format(pdata.pdata[project].name);
-    for (var i = 1; i < 5; i++) {
-        let step = step_json[i];
-        rep += "\n## %s:\n".format(step.description);
-        if (report[i] !== null) {
-            rep += report[i].replace(/(\r?\n)+$/, '');
-        } else {
-            rep += "Nothing entered yet...\n";
-        }
-        rep += "\n";
-    }
-    return rep;
-}
-
 function activity_tips(data) {
     let three_months_ago = moment().subtract(3, 'months');
     let txt = "";
@@ -1509,13 +1403,17 @@ function reflow(txt, chars) {
   return output;
 }
 
-function get_charter(data) {
-  let charter = data.pdata[project].charter;
+function get_charter(pdata) {
+  let charter = pdata.pdata[project].charter;
   
   let txt = reflow(charter);
   return txt;
 }
 
+function compile_check(pdata, editor) {
+  return editor.compile();
+}
+
 /******************************************
  Fetched from source/init.js
 ******************************************/
@@ -1526,6 +1424,9 @@ let project = location.search.substr(1);
 let loaded_from_draft = false;
 let PLACEHOLDER = '[Insert your own data here]';
 
+let editor = null;
+let stepper = null;
+
 if (project.length < 2) {
     GET("/reportingcycles.json", pre_splash, {});
 } else {
@@ -1549,11 +1450,9 @@ if (project.length < 2) {
  Fetched from source/primer.js
 ******************************************/
 
+// some glopbal vars for now - we'll get them localized soon enough.
 let pdata = {};
-let report = [null,null,null,null,null,null];
-let current_step = 0;
 let cycles = {};
-let draft_mode = false;
 let comments = {};
 
 function modal(txt) {
@@ -1590,152 +1489,17 @@ function prime_cycles(state, json) {
 }
 
 
-let step_json = {};
 function prime_steps(state, json) {
+    // Cancel spinner
     document.getElementById('wizard_spinner').style.display = 'none';
     document.getElementById('wrapper').style.display = 'block';
-    step_json = json.steps;
-    build_steps(0, true);
-}
-
-let hilite_timer = null;
-
-function build_steps(s, start, noclick, e) {
-    s = s || 0;
-    
-    let text = document.getElementById('step_text');
-    if (!start && text && text.value.length > 0 && current_step < 5) {
-        report[current_step] = text.value;
-    }
-   
-    text.innerText = '';
-    let step_changed = (s == current_step) ? false : true;
-    current_step = s;
-    
-    let stepParent = document.getElementById('steps');
-    stepParent.innerHTML = '';
-    for (var i = 0; i < step_json.length; i++) {
-        let element = step_json[i];
-        let wrapper = new HTML('div', {class: 'wizard-step-wrapper', onclick: 'build_steps(%u);'.format(i)});
-        let stepcircle = new HTML('div', {class: 'wizard-step'});
-        let stepicon = new HTML('i', {class: 'fas fa-%s'.format(element.icon)});
-        stepcircle.inject(stepicon);
-        let steptext = new HTML('div', {class: 'wizard-step-text'}, element.description);
-        wrapper.inject([stepcircle, steptext]);
-        if (s == i) {
-            stepcircle.setAttribute('class', 'wizard-step active');
-            steptext.setAttribute('class', 'wizard-step-text active');
-        }
-        if (i < s) {
-            stepcircle.setAttribute('class', 'wizard-step done');
-            steptext.setAttribute('class', 'wizard-step-text done');
-        }
-        stepParent.inject(wrapper);
-        if (i < step_json.length-1) {
-            let line = new HTML('div', {class: 'wizard-line'});
-            if (i < s) line.setAttribute('class', 'wizard-line done');
-            stepParent.inject(line);
-        }
-        if (s == i) {
-            let title = document.getElementById('step_title');
-            title.innerText = element.description;
-            let help = document.getElementById('step_help');
-            help.innerHTML = element.help || "No helper information available for this step...";
-            if (element.helpgenerator) {
-                let data = eval("%s(pdata);".format(element.helpgenerator));
-                help.innerHTML = data;
-            }
-            let text = document.getElementById('step_text');
-            text.placeholder = element.placeholder || "";
-            let hw = document.getElementById('help_wrapper');
-            if (element.noinput) {
-                text.style.display = 'none';
-            }
-            else text.style.display = 'inline';
-            text.style.height = (395 - hw.scrollHeight) + "px";
-            if (element.generator && !(report[s] && report[s].length > 0)) {
-                let data = eval("%s(pdata, compile_okay);".format(element.generator));
-                if (data === -1) text.style.display = 'none'; // hide if generator return -1
-                else text.value = data;
-            }
-            else if (report[s]) {
-                text.value = report[s];
-            } else {
-                text.value = '';
-            }
-            
-            // tips??
-            let tip = document.getElementById('tips');
-            if (element.tipgenerator) {
-                let data = eval("%s(pdata);".format(element.tipgenerator));
-                if (data && data.length > 0) {
-                    tip.innerHTML = data;
-                    tip.style.display = 'block';
-                } else {
-                    tip.style.display = 'none';
-                }
-            } else {
-                tip.style.display = 'none';
-            }
-        }
-    }
-    
-    let bp = document.getElementById('step_prev');
-    if (s == 0) bp.style.display = 'none';
-    else bp.style.display = 'block';
-    
-    let bn = document.getElementById('step_next');
-    if (s == step_json.length -1) bn.style.display = 'none';
-    else bn.style.display = 'block';
     
-    if (editor_type == 'unified') {
-        if (start) {
-            let template = "";
-            for (var i = 0; i < step_json.length; i++) {
-                let step = step_json[i];
-                if (!step.noinput || step.rawname) {
-                    template += "## %s:\n".format(step.rawname || step.description);
-                    if (step.generator) {
-                        let data = eval("%s(pdata);".format(step.generator));
-                        if (data && data.length > 0) template += data
-                    } else {
-                        template += PLACEHOLDER;
-                    }
-                    template += "\n\n";
-                }
-            }
-            document.getElementById('unified-report').value = template;
-        }
-        if (report_changed) hilite_sections();
-        
-        let step = step_json[s];
-        let helper = document.getElementById('unified-helper');
-        
-        helper.innerHTML = "<h5>%s:</h5>".format(step.description);
-        // Add in help
-        if (step.helpgenerator) {
-            let data = eval("%s(pdata);".format(step.helpgenerator));
-            helper.innerHTML += data;
-        } else if (step.help) {
-            helper.innerHTML += step.help;
-        }
-        
-        // Add tips?
-        if (step.tipgenerator) {
-            let data = eval("%s(pdata);".format(step.tipgenerator));
-            helper.innerHTML += data;
-        }
-        // If clicked to a section, move cursor
-        if (!noclick) {
-            set_position(step.description);
-        }
-        if (step_changed || !noclick)  mark_section(step.rawname||step.description);
-        else {
-            window.clearTimeout(hilite_timer);
-            if (event && event.keyCode == 13) mark_section(step.rawname||step.description);
-            else hilite_timer = window.setTimeout(() => { mark_section(step.rawname||step.description)}, 200);
-        }
-    }
+    // Create editor and stepper class
+    let editor = new UnifiedEditor('unified-report', json.steps);
+    let stepper = new ReportStepper('unified-steps', editor, json.steps, 'unified-helper');
+    editor.stepper = stepper;
+    stepper.pdata = pdata;
+    stepper.build(0, true);    
 }
 
 
@@ -1985,136 +1749,356 @@ function toggleView(id) {
 
 
 /******************************************
- Fetched from source/unified.js
+ Fetched from source/stepper.js
 ******************************************/
 
 
-function hilite_sections(b) {
-    if (highlighted && !b) return;
-    highlighted = true;
-    let hilites = [];
+// Left-hand side stepper for reports
+function ReportStepper(div, editor, layout, helper) {
     
+    // bind to object
+    if (typeof div == "string") this.object = document.getElementById(div);
+    else this.object = div;
+    
+    // bind to helper
+    if (typeof helper == "string") this.helper = document.getElementById(helper);
+    else this.helper = helper;
+    
+    this.layout = layout;
+    this.editor = editor;
+    this.timer = null; // highlight timer
+    this.step = -1;
+    this.changed = false;
+    this.pdata = null;
     
+    this.build =function (s, start, noclick, e) {
+        s = s || 0;
+        
+        this.changed = (s == this.step) ? false : true;
+        this.step = s;
+        
+        if (start) {
+            this.editor.reset();
+        }
+        
+        
+        if (this.changed) this.editor.highlight();
+        
+        // build the step div
+        this.object.innerHTML = '';
+        for (var i = 0; i < this.layout.length; i++) {
+            let element = this.layout[i];
+            let wrapper = new HTML('div', {class: 'wizard-step-wrapper'});
+            let x = i;
+            wrapper.addEventListener('click', () => { this.build(x);});
+            let stepcircle = new HTML('div', {class: 'wizard-step'});
+            let stepicon = new HTML('i', {class: 'fas fa-%s'.format(element.icon)});
+            stepcircle.inject(stepicon);
+            let steptext = new HTML('div', {class: 'wizard-step-text'}, element.description);
+            wrapper.inject([stepcircle, steptext]);
+            if (s == i) {
+                stepcircle.setAttribute('class', 'wizard-step active');
+                steptext.setAttribute('class', 'wizard-step-text active');
+            }
+            if (i < s) {
+                stepcircle.setAttribute('class', 'wizard-step done');
+                steptext.setAttribute('class', 'wizard-step-text done');
+            }
+            this.object.inject(wrapper);
+            if (i < step_json.length-1) {
+                let line = new HTML('div', {class: 'wizard-line'});
+                if (i < s) line.setAttribute('class', 'wizard-line done');
+                this.object.inject(line);
+            }
+        }
+            
+        let step = this.layout[s];
+        // If helper exists, show useful data
+        if (this.helper) {
+            this.helper.innerHTML = "<h5>%s:</h5>".format(step.description);
+            // Add in help
+            if (step.helpgenerator) {
+                let f = Function('a', 'b', "return %s(a, b);".format(step.helpgenerator));
+                data = f(this.pdata, this.editor)
+                this.helper.innerHTML += data;
+            } else if (step.help) {
+                this.helper.innerHTML += step.help;
+            }
+            // Add tips?
+            if (step.tipgenerator) {
+                let f = Function('a', 'b', "return %s(a,b);".format(step.tipgenerator));
+                data = f(this.pdata, this.editor)
+                this.helper.innerHTML += data;
+            }
+            // If clicked to a section, move cursor
+            if (!noclick) {
+                this.editor.set_position(step.description);
+            }
+            if (this.changed || !noclick)  this.editor.mark_section(step.rawname||step.description);
+            else {
+                window.clearTimeout(this.timer);
+                if (event && event.keyCode == 13) this.editor.mark_section(step.rawname||step.description);
+                else this.timer = window.setTimeout(() => { this.editor.mark_section(step.rawname||step.description)}, 200);
+            }
+        }
+    }
+}
+
+/******************************************
+ Fetched from source/unified.js
+******************************************/
+
+
+// Function that highlights headers and optional sectin in a unified editor
+function UnifiedEditor_highlight_sections(additional_text) {
+    // Don't highlight unless we haven't before or new text is noted
+    if (this.have_highligted && !additional_text) return;
+    
+    // Set which sections  highlight
+    let hilites = [];
+      // Headers are blue
     hilites.push({highlight: /^## [^\r\n]+:/mg, className: 'blue' });
+      // Placeholders are grey with red border
     hilites.push({highlight: PLACEHOLDER, className: 'none' });
     
+    // Capture text cursor position(s) before we continue.
     let x = $('#unified-report').selectionStart;
     let y = $('#unified-report').selectionEnd;
-        
-    if (b) {
+    
+    // If additional text is marked for highlighting, we'll have to
+    // first destroy any original highlighting, as it's params changed!
+    if (additional_text) {
         $('#unified-report').highlightWithinTextarea('destroy');
+        
+        // Sections are marked light green
         hilites.push({
-            highlight: b,
+            highlight: additional_text,
             className: 'green'
             });
     }
     
-    
-    $('#unified-report').highlightWithinTextarea({
+    // Run the highlighter on ourselves
+    $(this.object).highlightWithinTextarea({
             highlight: hilites
         });
+    
+    // If x == y (cursor is present and not marking characters),
+    // We'll force focus on ourselves as highlighting loses it.
     if (x == y) {
-        $('#unified-report').selectionStart = x;
-        $('#unified-report').selectionEnd = y;
-        $('#unified-report').focus();
+        $(this.object).selectionStart = x;
+        $(this.object).selectionEnd = y;
+        $(this.object).focus();
     }
 }
 
 
-let report_unified = "";
-let report_changed = true;
-let highlighted = false;
 
-function find_section(e) {
-    let tmp = document.getElementById('unified-report').value;
-    report_changed = (report_unified == tmp) ? false : true;
-    report_unified = tmp;
-    let spos = $('#unified-report').prop("selectionStart");
-    let helper = document.getElementById('unified-helper');
+// Function for figuring out WHERE we are in our report, cursor-wise
+function UnifiedEditor_find_section(e) {
+    let tmp = this.object.value;
+    this.changed = (this.report == tmp) ? false : true;
+    this.report = tmp;
+    let spos = this.object.selectionStart;
     
     // Hop to next newline, so marking the title will jump to the right section
-    while (report_unified[spos] != "\n" && spos < report_unified.length) spos++;
+    while (this.report[spos] != "\n" && spos < this.report.length) spos++;
     
-    let tprec = report_unified.substr(0, spos);
+    let tprec = this.report.substr(0, spos);
     let at_step = -1;
-    for (var i = 0; i < step_json.length; i++) {
-        let step = step_json[i];
+    for (var i = 0; i < this.layout.length; i++) {
+        let step = this.layout[i];
         let tline = "## %s:".format(step.rawname || step.description);
         if (tprec.indexOf(tline) != -1) {
             at_step = i;
         }
     }
     
-    if (at_step != -1) {
-        build_steps(at_step, false, true, e);
-        
-    } else {
-        helper.innerText = "";
-    }
+    if (at_step != -1 && this.stepper) {
+        this.stepper.build(at_step, false, true, e);
+    } 
 }
 
-function set_position(text) {
-    let editor = document.getElementById('unified-report');
-    let pos = editor.value.search(text);
-    if (pos && pos > 0) {
-        editor.selectionStart = (pos + text.length + 2);
-        editor.selectionEnd = (pos + text.length + 2);
-        editor.focus();
+
+// Quick shortcut to focusing somewhere in the report
+function UnifiedEditor_set_position(text) {
+    let pos = this.object.value.indexOf(text);
+    if (pos != -1) {
+        this.object.selectionStart = (pos + text.length + 2);
+        this.object.selectionEnd = (pos + text.length + 2);
+        this.object.focus();
     }
 }
 
 // Parses a unified report into sections
-function parse_unified(quiet) {
-    let sections = [];
-    let sX = 0;
-    let tmp = document.getElementById('unified-report').value;
+function UnifiedEditor_parse_report(quiet) {
+    this.sections = []; // Reset sections
+    let sX = 0; // sX is our moving cursor in the text as we parse.
+    let tmp = this.object.value; // get our textarea data
     while (tmp.length > 0) {
+      // Look for the next "## Foo Bar:" line
       let nextheader = tmp.match(/^## ([^\r\n]+)\r?\n/m);
       if (nextheader) {
-        if (!quiet) console.log("Found report header: %s".format(nextheader[0]))
+        if (!quiet) console.log("Found report header: %s".format(nextheader[0]));
         let title = nextheader[1];
         let spos = tmp.indexOf(nextheader[0]);
         if (spos != -1) {
-          sX += spos + nextheader[0].length;
+          sX += spos + nextheader[0].length; // move cursor
           sY = sX;
+          // ourr buffer past this header, find another one further down
           tmp = tmp.substr(spos + nextheader[0].length);
           let epos = tmp.search(/^## [^\r\n]+/m);
+          // if no further headers, use end of buffer as end pos.
           epos = (epos == -1) ? tmp.length : epos;
           let section = tmp.substr(0, epos);
+          // We got something(?), push to sections array.
           if (title.length > 2) {
-            sections.push({
-              title: title.replace(/:.*$/, ''),
+            this.sections.push({
+              title: title.replace(/:.*$/, ''), // crop away colon and any spaces following
               text: section,
               start: sX,
               end: sX + epos
             });
           }
           if (!quiet) console.log("Section contains:");
-          if (!quiet) console.log(section)
+          if (!quiet) console.log(section);
           tmp = tmp.substr(epos);
-        } else { break }
+        } else { break; }
       } else {
         if (!quiet) console.log("No more report headers found.");
       }
       
     }
-    return sections;
 }
 
 
-// Mark a section using the highlighter
-function mark_section(title) {
-    let sections = parse_unified(true);
+// Mark a section using the highlighter.
+function UnifiedEditor_mark_section(title) {
+    this.parse(true);
     let foundit = false;
-    for (var i = 0; i < sections.length; i++) {
-        if (sections[i].title == title && sections[i].text.indexOf(PLACEHOLDER) == -1 && sections[i].text.length > 4) {
+    for (var i = 0; i < this.sections.length; i++) {
+        let section = this.sections[i];
+        if (section.title == title && section.text.indexOf(PLACEHOLDER) == -1 && section.text.length > 4) {
             //console.log("Marking entire %s section from %u to %u".format(title, sections[i].start, sections[i].end))
-            hilite_sections(sections[i].text);
+            this.highlight(section.text);
             foundit = true;
-            break
+            break;
         }
     }
+    // If we don't know what to highlight, "reset" by highlighting a
+    // piece of text that doesn't exist. HACK HACK HACK.
     if (!foundit) {
-        hilite_sections("<-- EXTERMINATE -->");
+        this.highlight("<-- EXTERMINATE -->");
+    }
+}
+
+
+// Function for resetting a report to follow layout
+function UnifiedEditor_reset() {
+    this.report = "";
+    this.changed = true;
+    for (var i = 0; i < this.layout.length; i++) {
+        let step = this.layout[i];
+        if (!step.noinput || step.rawname) {
+            this.report += "## %s:\n".format(step.rawname || step.description);
+            if (step.generator) {
+                let f = Function('a', "return %s(a);".format(step.generator));
+                data = f(this.stepper.pdata)
+                if (data && data.length > 0) this.report += data
+            } else {
+                this.report += PLACEHOLDER;
+            }
+            this.report += "\n\n";
+        }
     }
-}
\ No newline at end of file
+    this.object.value = this.report;
+    this.sections = [];
+}
+
+// Function for compiling (validating) a report
+function UnifiedEditor_compile() {
+    this.compiles = true;
+    let text = "";
+      let required_sections = [];
+      this.parse();
+      for (var i = 0; i < this.layout.length; i++) {
+        let step = this.layout[i];
+        if (!step.noinput) {
+          let found = false;
+          required_sections.push(step.rawname||step.description);
+          for (var n = 0; n < this.sections.length; n++) {
+            if (this.sections[n].title == (step.rawname||step.description)) {
+              found = true;
+              if (this.sections[n].text.indexOf(PLACEHOLDER) != -1) {
+                console.log("Found placeholder text: " + PLACEHOLDER)
+                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: red;'>&#xF7;</span> <kbd>%s</kbd> contains placeholder text!</li>".format(this.sections[n].title);
+                this.compiles = false;
+              } else if (this.sections[n].text.length < 20) {
+                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: pink;'>&#8253;</span> <kbd>%s</kbd> seems a tad short?</li>".format(this.sections[n].title);
+              } else {
+                text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: green;'>&#x2713;</span> <kbd>%s</kbd> seems alright</li>".format(this.sections[n].title);
+                
+              }
+              break;
+            }
+          }
+          if (!found) {
+            this.compiles = false;
+            text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: red;'>&#xF7;</span> <kbd>%s</kbd> is missing from the report!</li>".format(step.description);
+          }
+        }
+        
+      }
+      
+      // Remark on additional sections not required
+      for (var n = 0; n < this.sections.length; n++) {
+          if (!required_sections.has(this.sections[n].title)) {
+            text += "<li><span style='display: inline-block; width: 20px; font-size: 18px; color: pink;'>&#8253;</span> Found unknown section <kbd>%s</kbd></li>".format(this.sections[n].title);
+          }
+      }
+     
+    
+    if (text.length > 0) {
+        text = "<h5>Report review results:</h5>The following remarks were logged by the report compiler:<br/><ul>" + text + "</ul>";
+    }
+    if (!this.compiles) {
+      text += "Your report could possibly use some more work, and that's okay! You can always save your current report as a draft and return later to work more on it. Drafts are saved for up to two months.";
+    }
+    else {
+        text += "That's it, your board report compiled a-okay and is potentially ready for submission! If you'd like more time to work on it, you can save it as a draft, and return later to make some final edits. Or you can publish it to the agenda via Whimsy.";
+    }
+    text += "<br/><button class='btn btn-warning' onclick='save_draft();'>Save as draft</button>"
+    if (this.compiles) text += " &nbsp; &nbsp; <button class='btn btn-success'>Publish via Whimsy</button>"
+    return text;
+}
+
+
+// This is the Unfied Editor for reports.
+function UnifiedEditor(div, layout) {
+    // Bind to our textarea, direct or via ID.
+    console.log(typeof div)
+    if (typeof div == "string") this.object = document.getElementById(div);
+    else this.object = div;
+    
+    this.layout = layout; // our JSON report layout (steps.json)
+    this.sections = []; // Sections we have or have found in the editor.
+    this.report = ""; // The combined report
+    this.stepper = null; // optional stepper class
+    
+    this.have_highligted = false; // whether we have highlighted before?
+    this.changed = false;  // whether report changed since last parse
+    this.compiles = false; // Whether compiles are okay
+    
+    // Function references
+    this.highlight = UnifiedEditor_highlight_sections;
+    this.mark_section = UnifiedEditor_mark_section;
+    this.parse = UnifiedEditor_parse_report;
+    this.set_position = UnifiedEditor_set_position;
+    this.reset = UnifiedEditor_reset;
+    this.find_section = UnifiedEditor_find_section;
+    this.compile = UnifiedEditor_compile;
+    
+    // set div events
+    this.object.addEventListener('keyup', () => { this.find_section(true); });
+    this.object.addEventListener('mouseup', () => { this.find_section(); });
+    
+}

Modified: comdev/reporter.apache.org/trunk/site/wizard/steps.json
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/steps.json?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/steps.json (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/steps.json Fri Aug  2 13:13:36 2019
@@ -46,7 +46,7 @@
         "description": "Review Report",
         "help": "That's it, we've compiled your report for you. Please review it. When it is time to file your report, you can submit it via the <a href='https://whimsy.apache.org/board/agenda/'>Whimsy Agenda Tool</a>.",
         "generator": "compile_report",
-        "helpgenerator": "check_compile",
+        "helpgenerator": "compile_check",
         "noinput": true
     }
     ]

Modified: comdev/reporter.apache.org/trunk/site/wizard/unified.html
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/unified.html?rev=1864224&r1=1864223&r2=1864224&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/unified.html (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/unified.html Fri Aug  2 13:13:36 2019
@@ -16,8 +16,8 @@ let editor_type = 'unified';
 </script>
 
 <title>ASF Board Report Wizard</title>
-<link rel="stylesheet" href="css/wizard.css?foo2"/>
-<link rel="stylesheet" href="highlighter/highlighter.css"/>
+<link rel="stylesheet" href="css/wizard.css?newstuff"/>
+<link rel="stylesheet" href="highlighter/highlighter.css?newstuff"/>
 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
 </head>
 <body>
@@ -26,37 +26,18 @@ let editor_type = 'unified';
   <div class="loader"></div>
  </div>
  
-   <div id="wrapper" style="display: none;">
+   <div id="wrapper" style="display: none;" class="unified">
     <h2 id="pname" style="text-align: center;">Board Report Wizard</h2>
-    <div id="steps">
+    <div id="unified-steps">
+     
      
-      
      </div>
     
     <div id="unified-editor">
-     <textarea id="unified-report" onmouseup="find_section();" onkeyup="find_section(event);"></textarea>
+     <textarea id="unified-report"></textarea>
     </div>
     <div id="unified-helper"></div>
     
-    <div id="wizard-content">
-     <div id="help_wrapper">
-     <h3 id="step_title">Project Activity:</h3>
-     <p id="step_help">
-      
-     </p>
-     </div>
-     <p style="text-align: center; width: 100%; height: 100%;">
-     <textarea id="step_text" style="width: 90%; height: 60%; margin: 0 auto;"
-placeholder="Example activity:
-- This quarter, we attended FooCon - it was a sounding success!
-- Apache Foo 1.2.3 was released on August 4th, 2019."></textarea><br/>
-<button id="step_prev" onclick="build_steps(current_step-1);" class="btn btn-info" style="float: left;">&#x2039;&#x2039; Previous step</button>
-<button id="step_next" onclick="build_steps(current_step+1);" class="btn btn-success" style="float: right;">&#x203A;&#x203A; Next step</button>
-</p>
-    </div>
-    
-    <div id="tips" class="wizard-tip"></div>
-    </div>
    
    <!-- Modal for notices -->
   <div class="modal fade" id="alert" role="dialog">
@@ -79,7 +60,7 @@ placeholder="Example activity:
   </div>
    
    <footer style="text-align: center; font-size: 0.8rem; clear: both;">
-    Powered by the <a href='?'>ASF Board Report Wizard</a>. Copyright&copy; 2019 <a href='https://community.apache.org'>Apache Community Development</a>.<br/>
+    Powered by the <a href='./'>ASF Board Report Wizard</a>. Copyright&copy; 2019 <a href='https://community.apache.org'>Apache Community Development</a>.<br/>
     Queries or other feedback should go to our mailing list: dev@community.apache.org
    </footer>
    
@@ -91,8 +72,8 @@ placeholder="Example activity:
 <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
 <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
-<script src="highlighter/highlighter.js" type="text/javascript"></script>
-<script src="js/wizard.js" type="text/javascript"></script>
+<script src="highlighter/highlighter.js?newstuff" type="text/javascript"></script>
+<script src="js/wizard.js?newstuff" type="text/javascript"></script>
 </body>
 </html>