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/12/16 10:35:32 UTC

svn commit: r1871647 - in /comdev/reporter.apache.org/trunk/site/wizard: beta.html index.html js/source/build.sh js/wizard-beta.js js/wizard.js

Author: humbedooh
Date: Mon Dec 16 10:35:31 2019
New Revision: 1871647

URL: http://svn.apache.org/viewvc?rev=1871647&view=rev
Log:
We're out of beta, really

Removed:
    comdev/reporter.apache.org/trunk/site/wizard/beta.html
    comdev/reporter.apache.org/trunk/site/wizard/js/wizard-beta.js
Modified:
    comdev/reporter.apache.org/trunk/site/wizard/index.html
    comdev/reporter.apache.org/trunk/site/wizard/js/source/build.sh
    comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js

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=1871647&r1=1871646&r2=1871647&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/index.html (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/index.html Mon Dec 16 10:35:31 2019
@@ -64,6 +64,6 @@
 <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-beta.js?unified-1.3" type="text/javascript"></script>
+<script src="js/wizard.js?unified-1.4" type="text/javascript"></script>
 </body>
 </html>

Modified: comdev/reporter.apache.org/trunk/site/wizard/js/source/build.sh
URL: http://svn.apache.org/viewvc/comdev/reporter.apache.org/trunk/site/wizard/js/source/build.sh?rev=1871647&r1=1871646&r2=1871647&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/source/build.sh (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/source/build.sh Mon Dec 16 10:35:31 2019
@@ -16,12 +16,12 @@ echo '/*
  limitations under the License.
 */
 // THIS IS AN AUTOMATICALLY COMBINED FILE. PLEASE EDIT source/*.js!!
-' > ../wizard-beta.js
+' > ../wizard.js
 # Warning: ls/sort order depends on the locale; this can affect the order of '.' and '_'
 # i.e. statistics.js and statistics_generator.js
 # so force the use of 'C'
 for f in `LC_ALL=C ls *.js`; do
-    printf "\n\n/******************************************\n Fetched from source/${f}\n******************************************/\n\n" >> ../wizard-beta.js
-    perl -0pe 's/\/\*.*?\*\/[\r\n]*//sm' ${f} >> ../wizard-beta.js
+    printf "\n\n/******************************************\n Fetched from source/${f}\n******************************************/\n\n" >> ../wizard.js
+    perl -0pe 's/\/\*.*?\*\/[\r\n]*//sm' ${f} >> ../wizard.js
 done
 echo "Done!"

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=1871647&r1=1871646&r2=1871647&view=diff
==============================================================================
--- comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js (original)
+++ comdev/reporter.apache.org/trunk/site/wizard/js/wizard.js Mon Dec 16 10:35:31 2019
@@ -91,7 +91,7 @@ async function GET(url, callback, state,
             let meta = {method: method, credentials: 'include', referrerPolicy: 'unsafe-url', headers: {'x-original-referral': document.referrer}};
             if (body) {
                 meta.body = body;
-                meta.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
+                meta.headers['Content-Type'] = 'application/json; charset=UTF-8';
             }
             console.log("putting %s in escrow...".format(url));
             async_escrow[pkey] = new Date(); // Log start of request in escrow dict
@@ -1024,11 +1024,10 @@ function save_draft() {
         'project': project,
         'action': 'save',
         'type': 'unified',
-        'report': JSON.stringify(draft_stepper.editor.report),
-        'report_compiled': draft_stepper.editor.report
+        'report': draft_stepper.editor.report
     }
     
-    let formdata = $.param(js);
+    let formdata = JSON.stringify(js);
     
     // Enable spinner, hide main wrapper
     document.getElementById('loader_text').innerText = "Saving draft...";
@@ -1036,7 +1035,7 @@ function save_draft() {
     document.getElementById('wrapper').style.display = 'none';
     document.getElementById("pname").style.display = 'none';
     
-    POST('drafts.py', draft_saved, {}, formdata);
+    POST('/api/drafts/save', draft_saved, {}, formdata);
 }
 
 function draft_saved(state, json) {
@@ -1062,7 +1061,7 @@ function draft_saved(state, json) {
 
 
 function load_draft(filename) {
-    GET('drafts.py?action=fetch&project=%s&filename=%s&type=%s'.format(project, filename, editor_type), read_draft, {});
+    GET('/api/drafts/fetch?%s'.format(filename), read_draft, {});
 }
 
 function read_draft(state, json) {
@@ -1081,7 +1080,7 @@ function read_draft(state, json) {
 
 function list_drafts(pdata, editor) {
   if (!saved_drafts) {
-    GET('drafts.py?action=index&project=%s&type=%s'.format(project, editor_type), show_draft_list, {stepper:editor.stepper});
+    GET('/api/drafts/index?%s'.format(project), show_draft_list, {stepper:editor.stepper});
     return "";
   }
   else {
@@ -1094,7 +1093,7 @@ function show_draft_list(state, json) {
   draft_stepper = state.stepper||draft_stepper; // hackish for now!
   if (!draft_stepper) return;
   let txt = "";
-  let filenames = Object.keys(saved_drafts);
+  let filenames = Object.keys(saved_drafts||{});
   if (filenames.length > 0) {
     txt += "<h6>Found the following saved drafts for %s:</h6>".format(project);
     txt += "<small style='font-size: 0.75rem;'><ul style='margin: 0px; padding: 10px;'>"
@@ -1124,7 +1123,7 @@ function delete_draft(filename) {
   if (!window.confirm("Are you sure you wish to delete %s?".format(filename))) {
     return;
   }
-  GET('drafts.py?action=delete&project=%s&filename=%s'.format(project, filename), deleted_draft, {filename: filename});
+  GET('/api/drafts/delete?%s'.format(filename), deleted_draft, {filename: filename});
 }
 
 function deleted_draft(state, json) {
@@ -1157,7 +1156,15 @@ function publish_report() {
         'report': draft_stepper.editor.report
     };
     
-    let formdata = $.param(js);
+    if (meta_data && meta_data.filed) {
+      if (!window.confirm("The report already exists in %s. Do you wish to force an update?".format(agendafile))) {
+        return;
+      }
+      js.digest = meta_data.report.digest;
+      js.attach = meta_data.report.attach;
+    }
+    
+    let formdata = JSON.stringify(js);
     
     // Enable spinner, hide main wrapper
     document.getElementById('loader_text').innerText = "Publishing report, hang on...";
@@ -1165,7 +1172,7 @@ function publish_report() {
     document.getElementById('wrapper').style.display = 'none';
     document.getElementById("pname").style.display = 'none';
     
-    POST('whimsy.py', report_published, {}, formdata);
+    POST('/api/whimsy/publish', report_published, {}, formdata);
 }
 
 function report_published(state, json) {
@@ -1180,6 +1187,26 @@ function report_published(state, json) {
   } else {
     modal("Something went wrong, and we couldn't publish your report.<br/>Please check with the Whimsy tool to see if there is already a report posted!");
   }
+  
+  // Force whimsy reload of report meta data
+  GET("/api/whimsy/agenda/refresh?%s".format(project), prime_meta, {noreset: true});
+    
+}
+
+function load_from_agenda() {
+  if (meta_data && meta_data.report && meta_data.filed) {
+    if (draft_stepper.editor.unsaved && !window.confirm("You have unsaved changes to your current draft. Do you wish to override these with the report in the agenda file??")) return;
+    draft_stepper.editor.object.value = meta_data.report.report;
+    draft_stepper.editor.report = meta_data.report.report;
+    window.setTimeout(() => { draft_stepper.editor.highlight() }, 250);
+    draft_stepper.build(0, false, false);
+    draft_stepper.editor.check_changes(true);
+    let reflower = document.getElementById('unified-reflow');
+    if (reflower) {
+      reflower.innerHTML = "Notice: Loaded most recent version of report from current agenda into editor.";
+      reflower.style.visibility = 'visible';
+    }
+  }
 }
 
 /******************************************
@@ -1310,13 +1337,24 @@ function generate_meta(data) {
     let txt = "<b>Founded: </b>%s (%s)<br/>".format(founded.format('YYYY-MM-DD'), age);
     txt += "<b>Chair: </b> %s<br/>".format(data.pdata[project].chair);
     txt += getReportDate(cycles, project);
+    txt += "<br/>"
+    if (meta_data.found) {
+      txt += "<b>Report expected this month:</b> YES<br/>";
+      txt += "<b>Filed to agenda: </b>";
+      if (meta_data.filed) {
+        txt += "<span style='color: #080;'>Yes</span> <a class='btn btn-primary btn-sm' href='javascript:void(load_from_agenda());'>Load from agenda</a>.";
+      } else {
+        txt += "<span style='color: #800;'>Not yet</span>";
+      }
+      txt += "<br/>"
+    }
     
     // Previous comments of note?
-    let cdates = Object.keys(comments.comments);
+    let cdates = Object.keys(meta_data.comments||{});
     cdates.sort();
-    if (comments && cdates.length > 0) {
+    if (meta_data && cdates.length > 0) {
       let date = cdates[cdates.length-1];
-      let comment = comments.comments[date];
+      let comment = meta_data.comments[date];
       
       // split and rejoin comments
       let ntxt = "";
@@ -1337,13 +1375,12 @@ function generate_meta(data) {
 
 function pre_splash(state, json) {
     cycles = json;
-    GET("/quickjson", splash, {});
+    GET("/api/overview", splash, {});
 }
 
 function splash(state, json, all) {
     pdata = json;
-    let html = document.body;
-    html.style.margin = '16px';
+    let html = document.getElementById('outer_wrapper');
     let link = all ? 'All projects (<a href="javascript:splash({}, pdata, false);">show only your projects</a>):' : 'Your projects (<a href="javascript:splash({}, pdata, true);">show all projects</a>):'
     html.innerHTML = '<h3>%s</h3>'.format(link);
     let tbl = new HTML('table', {cellpadding: '8px', style: {margin: '20px'}});
@@ -1351,7 +1388,7 @@ function splash(state, json, all) {
     hdr.inject([
         new HTML('td', {}, "Project:"),
         new HTML('td', {}, "Chair:"),
-        new HTML('td', {}, "Next report date:"),
+        new HTML('td', {}, "Next report due:"),
         new HTML('td', {}, "Editor:"),
         new HTML('td', {}, "Full statistics:"),
     ])
@@ -1370,7 +1407,7 @@ function splash(state, json, all) {
                 ccolor = '#195';
             }
             let tr = new HTML('tr');
-            let rd = new HTML('td', {}, getReportDate(cycles, key, true));
+            let rd = new HTML('td', {}, getReportDate(cycles, key, "due"));
             let elink = new HTML('td', {}, new HTML('a', {href: '?%s'.format(key)}, "Board report editor"));
             let slink = new HTML('td', {}, new HTML('a', {href: 'statistics?%s'.format(key)}, "Full statistics for %s".format(tlpname)));
             let title = new HTML('td', {}, new HTML('b', {}, tlpname));
@@ -1693,6 +1730,7 @@ function activity_tips(data) {
 
 // Quick check for reflow needs
 function should_reflow(txt, chars) {
+  if (typeof txt != 'string') return false;
   chars = chars || 80;
   let lines = txt.split(/[\r\n]+/g);
   for (var i = 0; i < lines.length; i++) {
@@ -1702,6 +1740,7 @@ function should_reflow(txt, chars) {
 }
 
 function reflow(txt, chars) {
+  if (typeof txt != 'string') return ""; 
   chars = chars || 80;
   let words = txt.match(/([\S+?]+\s*)/mg);
   if (!words) return txt;
@@ -1743,6 +1782,7 @@ function show_examples(examples, title)
   modal(out, title);
 }
 
+
 /******************************************
  Fetched from source/init.js
 ******************************************/
@@ -1776,7 +1816,7 @@ function init_wizard(so) {
         console.log("Initializing escrow checks");
         window.setInterval(escrow_check, 250);
         
-        GET("/quickjson?%s".format(project), prime_wizard, {});
+        GET("/api/overview?%s".format(project), prime_wizard, {});
     }
 }
 document.body.addEventListener('keydown', () => { if (event.keyCode == 27) $("#alert").modal('hide'); });
@@ -1789,7 +1829,7 @@ document.body.addEventListener('keydown'
 // some glopbal vars for now - we'll get them localized soon enough.
 let pdata = {};
 let cycles = {};
-let comments = {};
+let meta_data = {};
 
 function modal(txt, title = 'Notification') {
     document.getElementById('alert_text').innerHTML = txt;
@@ -1815,12 +1855,13 @@ function prime_wizard(state, json) {
     if (statsonly) {
         GET("/reportingcycles.json", prime_cycles, {});
     } else {
-        GET("comments.py?project=%s".format(project), prime_comments, {})
+        GET("/api/whimsy/agenda?%s".format(project), prime_meta, {})
     }
 }
 
-function prime_comments(state, json) {
-    comments = json;
+function prime_meta(state, json) {
+    meta_data = json;
+    if (state && state.noreset) return;
     GET("/reportingcycles.json", prime_cycles, {})
 }
 
@@ -1840,6 +1881,7 @@ function prime_steps(state, json) {
         let editor = new UnifiedEditor('unified-report', json.steps);
         let stepper = new ReportStepper('unified-steps', editor, json.steps, 'unified-helper');
         editor.stepper = stepper;
+        draft_stepper = stepper;
         stepper.pdata = pdata;
         stepper.build(0, true);
     }
@@ -1948,7 +1990,21 @@ function getReportDate(json, pmc, dateOn
 		nextdate = dates.shift();
 	}
 	if (agenda) return "board_agenda_%s.txt".format(moment(nextdate).format('YYYY_MM_DD'));
-	if (dateOnly) return nextdate ? (nextdate.toDateString() + " ("  + moment(nextdate).fromNow() + ")"): "Unknown(?)";
+	if (dateOnly) {
+		// agenda date or due date?
+		if (dateOnly == "due") {
+			nextdate.setDate(nextdate.getDate() - 2); // Due 48 hours prior to board meeting.
+			let rightnow = new Date();
+			if (nextdate && Math.abs(rightnow - nextdate) < (86400*2*1000)) {
+				let fromnow = moment(nextdate).fromNow();
+				if (nextdate && Math.abs(rightnow - nextdate) < (86400*1000)) fromnow = "TODAY";
+				return new HTML('span', {style: { color: '#711'}}, nextdate.toDateString() + " ("  + fromnow + ")");
+			}
+			return nextdate ? (nextdate.toDateString() + " ("  + moment(nextdate).fromNow() + ")"): "Unknown(?)";
+		} else {
+			return nextdate ? (nextdate.toDateString() + " ("  + moment(nextdate).fromNow() + ")"): "Unknown(?)";
+		}
+	}
 	let txt = "";
 	txt += "<b>Reporting schedule:</b> " + (json[pmc] ? formatRm(json[pmc]) : "Unknown(?)") + "<br>"
 	txt += "<b>Next report date: " + (nextdate ? nextdate.toDateString() : "Unknown(?)") + "</b>"
@@ -2097,6 +2153,45 @@ function toggleView(id) {
 
 
 /******************************************
+ Fetched from source/statistics.js
+******************************************/
+
+
+function StatisticsPage(layout, pdata) {
+    let wrapper = document.getElementById('wrapper');
+    wrapper.style.padding = '8px';
+    wrapper.style.height = 'auto';
+    wrapper.innerHTML = "";
+    for (var i = 0; i < layout.length; i++) {
+        let step = layout[i];
+        if (step.statsgenerator||step.tipgenerator) {
+            let thtml = new HTML('p');
+            let f = Function('a', 'b', "return %s(a,b);".format(step.statsgenerator||step.tipgenerator));
+            data = f(pdata, {});
+            if (typeof data == 'string') thtml.innerHTML += data;
+            else if (typeof data == 'object') thtml.inject(data);
+            thtml.inject(new HTML('hr'));
+            wrapper.inject(thtml);
+        }
+    }
+    
+    headers = $(wrapper).find("h4");
+    let toc = "<ul style='background: #3333;'>";
+    for (var i = 0; i < headers.length; i++) {
+        let t = headers[i].innerText.replace(/:.*$/, '');
+        let id = t.replace(/\s+/g, '').toLowerCase();
+        headers[i].setAttribute('id', id);
+        toc += "<li style='display: inline-block; margin-left: 24px;'><a href='#%s'>%s</a></li>".format(id, t);
+    }
+    toc += "</ul>";
+    let twrap = new HTML('div');
+    twrap.innerHTML = toc;
+    wrapper.insertBefore(twrap, wrapper.childNodes[0]);
+    
+}
+
+
+/******************************************
  Fetched from source/statistics_generator.js
 ******************************************/
 
@@ -2487,8 +2582,8 @@ function statistics_health(data) {
                     }
                 }
                 cols[0].push(date);
-                cols[1].push(c);
-                cols[2].push(o);
+                cols[1].push(o);
+                cols[2].push(c);
             }
             let cutoff = moment.utc().subtract(13, 'weeks').startOf('week').weekday(4);
             let chartdiv = new HTML('div', {
@@ -2723,8 +2818,8 @@ function statistics_health(data) {
                     }
                 }
                 cols[0].push(date);
-                cols[1].push(c);
-                cols[2].push(o);
+                cols[1].push(o);
+                cols[2].push(c);
             }
             let cutoff = moment.utc().subtract(13, 'weeks').startOf('week').weekday(4);
             let chartdiv = new HTML('div', {
@@ -2851,8 +2946,8 @@ function statistics_health(data) {
                     }
                 }
                 cols[0].push(date);
-                cols[1].push(c);
-                cols[2].push(o);
+                cols[1].push(o);
+                cols[2].push(c);
             }
             let cutoff = moment.utc().subtract(13, 'weeks').startOf('week').weekday(4);
             let chartdiv = new HTML('div', {
@@ -3071,45 +3166,6 @@ function statistics_releases(data) {
 }
 
 /******************************************
- Fetched from source/statistics.js
-******************************************/
-
-
-function StatisticsPage(layout, pdata) {
-    let wrapper = document.getElementById('wrapper');
-    wrapper.style.padding = '8px';
-    wrapper.style.height = 'auto';
-    wrapper.innerHTML = "";
-    for (var i = 0; i < layout.length; i++) {
-        let step = layout[i];
-        if (step.statsgenerator||step.tipgenerator) {
-            let thtml = new HTML('p');
-            let f = Function('a', 'b', "return %s(a,b);".format(step.statsgenerator||step.tipgenerator));
-            data = f(pdata, {});
-            if (typeof data == 'string') thtml.innerHTML += data;
-            else if (typeof data == 'object') thtml.inject(data);
-            thtml.inject(new HTML('hr'));
-            wrapper.inject(thtml);
-        }
-    }
-    
-    headers = $(wrapper).find("h4");
-    let toc = "<ul style='background: #3333;'>";
-    for (var i = 0; i < headers.length; i++) {
-        let t = headers[i].innerText.replace(/:.*$/, '');
-        let id = t.replace(/\s+/g, '').toLowerCase();
-        headers[i].setAttribute('id', id);
-        toc += "<li style='display: inline-block; margin-left: 24px;'><a href='#%s'>%s</a></li>".format(id, t);
-    }
-    toc += "</ul>";
-    let twrap = new HTML('div');
-    twrap.innerHTML = toc;
-    wrapper.insertBefore(twrap, wrapper.childNodes[0]);
-    
-}
-
-
-/******************************************
  Fetched from source/stepper.js
 ******************************************/
 
@@ -3150,7 +3206,7 @@ function ReportStepper(div, editor, layo
         
         if (this.changed) this.editor.highlight();
         // skip building if nothing changed
-        if (!this.changed && !start && this.editor.report == this.editor.last_cursor_report) return;
+        if (!this.changed && !start && this.editor.report == this.editor.last_cursor_report && s != -1) return;
         this.editor.last_cursor_report = this.editor.report;
         
         // build the step div
@@ -3258,10 +3314,12 @@ function UnifiedEditor_highlight_section
     // Set which sections  highlight
     let hilites = [];
       // Headers are blue
-    hilites.push({highlight: /^## [^\r\n]+:/mg, className: 'blue' });
+    hilites.push({highlight: /^## [^\r\n]+:?/mg, className: 'blue' });
       // Placeholders are grey with red border
     hilites.push({highlight: PLACEHOLDER, className: 'none' });
-    
+     // <private> sections
+     hilites.push({highlight: /<private>[\s\S]+?<\/private>/i, className: 'classified' });
+     
     // Capture text cursor position(s) before we continue.
     let x = $('#unified-report').prop('selectionStart');
     let y = $('#unified-report').prop('selectionEnd');
@@ -3346,7 +3404,7 @@ function UnifiedEditor_find_section(e) {
     } else {
         for (var i = 0; i < this.layout.length; i++) {
             let step = this.layout[i];
-            let tline = "## %s:".format(step.rawname || step.description);
+            let tline = "## %s".format(step.rawname || step.description);
             if (tprec.indexOf(tline) != -1) {
                 at_step = i;
             }
@@ -3421,7 +3479,7 @@ function UnifiedEditor_mark_section(titl
     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))
+            //console.log("Marking entire %s section from %u to %u".format(title, this.sections[i].start, this.sections[i].end))
             this.highlight(section.text);
             foundit = true;
             break;
@@ -3437,6 +3495,11 @@ function UnifiedEditor_mark_section(titl
 
 // Function for resetting a report to follow layout
 function UnifiedEditor_reset() {
+    // Check whether we have a report in agenda, if so reset to that.
+    if (meta_data && meta_data.report && meta_data.filed) {
+        load_from_agenda();
+        return
+    }
     this.report = "";
     this.changed = true;
     for (var i = 0; i < this.layout.length; i++) {
@@ -3517,7 +3580,11 @@ function UnifiedEditor_compile() {
         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 onclick='publish_report();' class='btn btn-success'>Publish via Whimsy</button>"
+    if (!meta_data.found) {
+        text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled title='Your project is not listed in the current agenda!'>Publish via Whimsy</button>";
+        text += "<br/><span style='color: maroon;'>Your project is not expected to report this month. You may save drafts but you cannot publish yet.</span>";
+    }
+    else if (this.compiles) text += " &nbsp; &nbsp; <button onclick='publish_report();' class='btn btn-success'>Publish via Whimsy</button>"
     else text += " &nbsp; &nbsp; <button class='btn btn-secondary' disabled title='Please fix the above issues before you can publish'>Publish via Whimsy</button>"
     return text;
 }
@@ -3532,6 +3599,7 @@ function UnifiedEditor_check_changes(for
         this.stepper.helper.inject(saver);
     }
     if (this.report != this.report_saved) {
+        this.unsaved = true;
         if (saver) {
             saver.innerText = "Current changes not saved yet - ";
             let btn = new HTML('button', { onclick: 'save_draft();', class: 'btn btn-warning btn-sm'}, 'Save draft');
@@ -3543,6 +3611,7 @@ function UnifiedEditor_check_changes(for
                 
         }
     } else if (saver) {
+        this.unsaved = false;
         saver.style.display = 'none';
         window.onbeforeunload = null;
     }