<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://projectrebearth.com/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AChangelogEditor.js</id>
	<title>MediaWiki:ChangelogEditor.js - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://projectrebearth.com/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AChangelogEditor.js"/>
	<link rel="alternate" type="text/html" href="https://projectrebearth.com/index.php?title=MediaWiki:ChangelogEditor.js&amp;action=history"/>
	<updated>2026-04-16T13:12:04Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>https://projectrebearth.com/index.php?title=MediaWiki:ChangelogEditor.js&amp;diff=1149&amp;oldid=prev</id>
		<title>East6 at 14:36, 4 March 2026</title>
		<link rel="alternate" type="text/html" href="https://projectrebearth.com/index.php?title=MediaWiki:ChangelogEditor.js&amp;diff=1149&amp;oldid=prev"/>
		<updated>2026-03-04T14:36:42Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 14:36, 4 March 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l25&quot;&gt;Line 25:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 25:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;                 else if (k === &amp;#039;innerHTML&amp;#039;) node.innerHTML = attrs[k];&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;                 else if (k === &amp;#039;innerHTML&amp;#039;) node.innerHTML = attrs[k];&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;                 else if (k.indexOf(&amp;#039;on&amp;#039;) === 0) node.addEventListener(k.slice(2).toLowerCase(), attrs[k]);&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;                 else if (k.indexOf(&amp;#039;on&amp;#039;) === 0) node.addEventListener(k.slice(2).toLowerCase(), attrs[k]);&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;                 else node.setAttribute(k, attrs[k]);&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;                 else &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;if (attrs[k] != null) &lt;/ins&gt;node.setAttribute(k, attrs[k]);&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;             });&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;             });&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;         }&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;         }&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;

&lt;!-- diff cache key project_rebearth_wiki:diff:1.41:old-1142:rev-1149:php=table --&gt;
&lt;/table&gt;</summary>
		<author><name>East6</name></author>
	</entry>
	<entry>
		<id>https://projectrebearth.com/index.php?title=MediaWiki:ChangelogEditor.js&amp;diff=1142&amp;oldid=prev</id>
		<title>East6: Created page with &quot;(function () {     &#039;use strict&#039;;      var DATA_PAGE = &#039;Module:Changelog/data.json&#039;;     var PER_PAGE = 15;     var VALID_TAGS = [&#039;New&#039;, &#039;Balance&#039;, &#039;Fix&#039;, &#039;QoL&#039;, &#039;Performance&#039;];      var entries = [];     var originalJSON = &#039;&#039;;     var currentPage = 0;     var editToken = null;     var editingIndex = null;     var allPageTitles = [];     var dirty = false;      var root = document.getElementById(&#039;rb-changelog-editor&#039;);     if (!root) return;      function el(tag, attrs, c...&quot;</title>
		<link rel="alternate" type="text/html" href="https://projectrebearth.com/index.php?title=MediaWiki:ChangelogEditor.js&amp;diff=1142&amp;oldid=prev"/>
		<updated>2026-03-04T14:25:57Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;(function () {     &amp;#039;use strict&amp;#039;;      var DATA_PAGE = &amp;#039;Module:Changelog/data.json&amp;#039;;     var PER_PAGE = 15;     var VALID_TAGS = [&amp;#039;New&amp;#039;, &amp;#039;Balance&amp;#039;, &amp;#039;Fix&amp;#039;, &amp;#039;QoL&amp;#039;, &amp;#039;Performance&amp;#039;];      var entries = [];     var originalJSON = &amp;#039;&amp;#039;;     var currentPage = 0;     var editToken = null;     var editingIndex = null;     var allPageTitles = [];     var dirty = false;      var root = document.getElementById(&amp;#039;rb-changelog-editor&amp;#039;);     if (!root) return;      function el(tag, attrs, c...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;(function () {&lt;br /&gt;
    &amp;#039;use strict&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
    var DATA_PAGE = &amp;#039;Module:Changelog/data.json&amp;#039;;&lt;br /&gt;
    var PER_PAGE = 15;&lt;br /&gt;
    var VALID_TAGS = [&amp;#039;New&amp;#039;, &amp;#039;Balance&amp;#039;, &amp;#039;Fix&amp;#039;, &amp;#039;QoL&amp;#039;, &amp;#039;Performance&amp;#039;];&lt;br /&gt;
&lt;br /&gt;
    var entries = [];&lt;br /&gt;
    var originalJSON = &amp;#039;&amp;#039;;&lt;br /&gt;
    var currentPage = 0;&lt;br /&gt;
    var editToken = null;&lt;br /&gt;
    var editingIndex = null;&lt;br /&gt;
    var allPageTitles = [];&lt;br /&gt;
    var dirty = false;&lt;br /&gt;
&lt;br /&gt;
    var root = document.getElementById(&amp;#039;rb-changelog-editor&amp;#039;);&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    function el(tag, attrs, children) {&lt;br /&gt;
        var node = document.createElement(tag);&lt;br /&gt;
        if (attrs) {&lt;br /&gt;
            Object.keys(attrs).forEach(function (k) {&lt;br /&gt;
                if (k === &amp;#039;className&amp;#039;) node.className = attrs[k];&lt;br /&gt;
                else if (k === &amp;#039;textContent&amp;#039;) node.textContent = attrs[k];&lt;br /&gt;
                else if (k === &amp;#039;innerHTML&amp;#039;) node.innerHTML = attrs[k];&lt;br /&gt;
                else if (k.indexOf(&amp;#039;on&amp;#039;) === 0) node.addEventListener(k.slice(2).toLowerCase(), attrs[k]);&lt;br /&gt;
                else node.setAttribute(k, attrs[k]);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        if (children) {&lt;br /&gt;
            (Array.isArray(children) ? children : [children]).forEach(function (c) {&lt;br /&gt;
                if (c == null) return;&lt;br /&gt;
                node.appendChild(typeof c === &amp;#039;string&amp;#039; ? document.createTextNode(c) : c);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function todayISO() {&lt;br /&gt;
        var d = new Date();&lt;br /&gt;
        return d.getFullYear() + &amp;#039;-&amp;#039; +&lt;br /&gt;
            String(d.getMonth() + 1).padStart(2, &amp;#039;0&amp;#039;) + &amp;#039;-&amp;#039; +&lt;br /&gt;
            String(d.getDate()).padStart(2, &amp;#039;0&amp;#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function generateId(date) {&lt;br /&gt;
        var parts = date.split(&amp;#039;-&amp;#039;);&lt;br /&gt;
        var dd = parts[2], mm = parts[1], yy = parts[0].slice(2);&lt;br /&gt;
        var prefix = dd + mm + yy;&lt;br /&gt;
        var maxNum = 0;&lt;br /&gt;
        entries.forEach(function (e) {&lt;br /&gt;
            if (e.id &amp;amp;&amp;amp; e.id.indexOf(prefix) === 0) {&lt;br /&gt;
                var num = parseInt(e.id.slice(prefix.length), 10);&lt;br /&gt;
                if (num &amp;gt; maxNum) maxNum = num;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        return prefix + (maxNum + 1);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function markDirty() {&lt;br /&gt;
        dirty = true;&lt;br /&gt;
        render();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getUnsavedCount() {&lt;br /&gt;
        if (!dirty) return 0;&lt;br /&gt;
        var currentJSON = JSON.stringify(entries, null, 2);&lt;br /&gt;
        return currentJSON !== originalJSON ? 1 : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function computeChangeSummary() {&lt;br /&gt;
        var orig;&lt;br /&gt;
        try { orig = JSON.parse(originalJSON); } catch (e) { orig = []; }&lt;br /&gt;
        var origMap = {};&lt;br /&gt;
        orig.forEach(function (e) { origMap[e.id] = JSON.stringify(e); });&lt;br /&gt;
        var curMap = {};&lt;br /&gt;
        entries.forEach(function (e) { curMap[e.id] = JSON.stringify(e); });&lt;br /&gt;
&lt;br /&gt;
        var added = [], modified = [], deleted = [];&lt;br /&gt;
        entries.forEach(function (e) {&lt;br /&gt;
            if (!origMap[e.id]) added.push(e);&lt;br /&gt;
            else if (origMap[e.id] !== curMap[e.id]) modified.push(e);&lt;br /&gt;
        });&lt;br /&gt;
        orig.forEach(function (e) {&lt;br /&gt;
            if (!curMap[e.id]) deleted.push(e);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        var origIds = orig.map(function (e) { return e.id; }).join(&amp;#039;,&amp;#039;);&lt;br /&gt;
        var curIds = entries.map(function (e) { return e.id; }).join(&amp;#039;,&amp;#039;);&lt;br /&gt;
        var reordered = origIds !== curIds &amp;amp;&amp;amp; added.length === 0 &amp;amp;&amp;amp; deleted.length === 0;&lt;br /&gt;
&lt;br /&gt;
        return { added: added, modified: modified, deleted: deleted, reordered: reordered };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function apiGet(params) {&lt;br /&gt;
        var url = mw.util.wikiScript(&amp;#039;api&amp;#039;) + &amp;#039;?&amp;#039; + Object.keys(params).map(function (k) {&lt;br /&gt;
            return encodeURIComponent(k) + &amp;#039;=&amp;#039; + encodeURIComponent(params[k]);&lt;br /&gt;
        }).join(&amp;#039;&amp;amp;&amp;#039;);&lt;br /&gt;
        return fetch(url).then(function (r) { return r.json(); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchData() {&lt;br /&gt;
        var url = mw.util.wikiScript(&amp;#039;index&amp;#039;) + &amp;#039;?title=&amp;#039; + encodeURIComponent(DATA_PAGE) + &amp;#039;&amp;amp;action=raw&amp;#039;;&lt;br /&gt;
        return fetch(url).then(function (r) {&lt;br /&gt;
            if (!r.ok) throw new Error(&amp;#039;Failed to fetch data: &amp;#039; + r.status);&lt;br /&gt;
            return r.text();&lt;br /&gt;
        }).then(function (text) {&lt;br /&gt;
            entries = JSON.parse(text);&lt;br /&gt;
            originalJSON = JSON.stringify(entries, null, 2);&lt;br /&gt;
            dirty = false;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchEditToken() {&lt;br /&gt;
        return apiGet({ action: &amp;#039;query&amp;#039;, meta: &amp;#039;tokens&amp;#039;, type: &amp;#039;csrf&amp;#039;, format: &amp;#039;json&amp;#039; })&lt;br /&gt;
            .then(function (data) {&lt;br /&gt;
                editToken = data.query.tokens.csrftoken;&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function saveData(summary) {&lt;br /&gt;
        var content = JSON.stringify(entries, null, 2);&lt;br /&gt;
        var params = new URLSearchParams();&lt;br /&gt;
        params.append(&amp;#039;action&amp;#039;, &amp;#039;edit&amp;#039;);&lt;br /&gt;
        params.append(&amp;#039;title&amp;#039;, DATA_PAGE);&lt;br /&gt;
        params.append(&amp;#039;text&amp;#039;, content);&lt;br /&gt;
        params.append(&amp;#039;summary&amp;#039;, summary);&lt;br /&gt;
        params.append(&amp;#039;contentformat&amp;#039;, &amp;#039;application/json&amp;#039;);&lt;br /&gt;
        params.append(&amp;#039;contentmodel&amp;#039;, &amp;#039;json&amp;#039;);&lt;br /&gt;
        params.append(&amp;#039;token&amp;#039;, editToken);&lt;br /&gt;
        params.append(&amp;#039;format&amp;#039;, &amp;#039;json&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
        return fetch(mw.util.wikiScript(&amp;#039;api&amp;#039;), {&lt;br /&gt;
            method: &amp;#039;POST&amp;#039;,&lt;br /&gt;
            body: params,&lt;br /&gt;
            credentials: &amp;#039;same-origin&amp;#039;&lt;br /&gt;
        }).then(function (r) { return r.json(); }).then(function (data) {&lt;br /&gt;
            if (data.error) {&lt;br /&gt;
                if (data.error.code === &amp;#039;badtoken&amp;#039;) {&lt;br /&gt;
                    return fetchEditToken().then(function () {&lt;br /&gt;
                        params.set(&amp;#039;token&amp;#039;, editToken);&lt;br /&gt;
                        return fetch(mw.util.wikiScript(&amp;#039;api&amp;#039;), {&lt;br /&gt;
                            method: &amp;#039;POST&amp;#039;,&lt;br /&gt;
                            body: params,&lt;br /&gt;
                            credentials: &amp;#039;same-origin&amp;#039;&lt;br /&gt;
                        }).then(function (r) { return r.json(); });&lt;br /&gt;
                    }).then(function (data2) {&lt;br /&gt;
                        if (data2.error) throw new Error(data2.error.info);&lt;br /&gt;
                        return data2;&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                throw new Error(data.error.info);&lt;br /&gt;
            }&lt;br /&gt;
            originalJSON = JSON.stringify(entries, null, 2);&lt;br /&gt;
            dirty = false;&lt;br /&gt;
            return data;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function searchPageTitles(query) {&lt;br /&gt;
        if (!query || query.length &amp;lt; 2) return [];&lt;br /&gt;
        var q = query.toLowerCase();&lt;br /&gt;
        return allPageTitles.filter(function (t) {&lt;br /&gt;
            return t.toLowerCase().indexOf(q) !== -1;&lt;br /&gt;
        }).slice(0, 10);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchAllPageTitles() {&lt;br /&gt;
        var titles = [];&lt;br /&gt;
        function fetchBatch(apcontinue) {&lt;br /&gt;
            var params = {&lt;br /&gt;
                action: &amp;#039;query&amp;#039;,&lt;br /&gt;
                list: &amp;#039;allpages&amp;#039;,&lt;br /&gt;
                aplimit: &amp;#039;500&amp;#039;,&lt;br /&gt;
                apnamespace: &amp;#039;0&amp;#039;,&lt;br /&gt;
                format: &amp;#039;json&amp;#039;&lt;br /&gt;
            };&lt;br /&gt;
            if (apcontinue) params.apcontinue = apcontinue;&lt;br /&gt;
            return apiGet(params).then(function (data) {&lt;br /&gt;
                data.query.allpages.forEach(function (p) { titles.push(p.title); });&lt;br /&gt;
                if (data[&amp;#039;continue&amp;#039;] &amp;amp;&amp;amp; data[&amp;#039;continue&amp;#039;].apcontinue) {&lt;br /&gt;
                    return fetchBatch(data[&amp;#039;continue&amp;#039;].apcontinue);&lt;br /&gt;
                }&lt;br /&gt;
                allPageTitles = titles;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        return fetchBatch(null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&amp;#039;beforeunload&amp;#039;, function (e) {&lt;br /&gt;
        if (dirty) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.returnValue = &amp;#039;&amp;#039;;&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function render() {&lt;br /&gt;
        root.innerHTML = &amp;#039;&amp;#039;;&lt;br /&gt;
        root.appendChild(renderApp());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderApp() {&lt;br /&gt;
        var wrap = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-wrap&amp;#039; });&lt;br /&gt;
        wrap.appendChild(renderHeader());&lt;br /&gt;
        if (editingIndex !== null) {&lt;br /&gt;
            wrap.appendChild(renderForm());&lt;br /&gt;
        }&lt;br /&gt;
        wrap.appendChild(renderList());&lt;br /&gt;
        wrap.appendChild(renderPagination());&lt;br /&gt;
        return wrap;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderHeader() {&lt;br /&gt;
        var title = el(&amp;#039;h2&amp;#039;, null, &amp;#039;Changelog Editor&amp;#039;);&lt;br /&gt;
        var unsaved = dirty ? el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-badge&amp;#039;, textContent: &amp;#039;unsaved&amp;#039; }) : null;&lt;br /&gt;
&lt;br /&gt;
        var addBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-add&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;+ Add Entry&amp;#039;,&lt;br /&gt;
            onClick: function () {&lt;br /&gt;
                editingIndex = -1;&lt;br /&gt;
                currentPage = 0;&lt;br /&gt;
                render();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        var saveBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-primary&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;Save All&amp;#039;,&lt;br /&gt;
            disabled: !dirty ? &amp;#039;disabled&amp;#039; : null,&lt;br /&gt;
            onClick: function () { showConfirmDialog(); }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        var actions = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-header-actions&amp;#039; }, [addBtn, saveBtn, unsaved]);&lt;br /&gt;
        return el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-header&amp;#039; }, [title, actions]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderList() {&lt;br /&gt;
        var list = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-list&amp;#039; });&lt;br /&gt;
        if (entries.length === 0) {&lt;br /&gt;
            list.appendChild(el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-empty&amp;#039;, textContent: &amp;#039;No changelog entries yet.&amp;#039; }));&lt;br /&gt;
            return list;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var start = currentPage * PER_PAGE;&lt;br /&gt;
        var end = Math.min(start + PER_PAGE, entries.length);&lt;br /&gt;
        for (var i = start; i &amp;lt; end; i++) {&lt;br /&gt;
            list.appendChild(renderEntry(i));&lt;br /&gt;
        }&lt;br /&gt;
        return list;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderEntry(idx) {&lt;br /&gt;
        var entry = entries[idx];&lt;br /&gt;
        var tag = el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-tag rb-ce-tag-&amp;#039; + entry.tag, textContent: entry.tag });&lt;br /&gt;
        var date = el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-entry-date&amp;#039;, textContent: entry.date });&lt;br /&gt;
        var meta = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-entry-meta&amp;#039; }, [tag, date]);&lt;br /&gt;
&lt;br /&gt;
        var text = el(&amp;#039;p&amp;#039;, { className: &amp;#039;rb-ce-entry-text&amp;#039;, textContent: entry.text });&lt;br /&gt;
        var idSpan = el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-entry-id&amp;#039;, textContent: &amp;#039;ID: &amp;#039; + entry.id });&lt;br /&gt;
        var body = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-entry-body&amp;#039; }, [text, idSpan]);&lt;br /&gt;
&lt;br /&gt;
        if ((entry.pages &amp;amp;&amp;amp; entry.pages.length) || (entry.exclude &amp;amp;&amp;amp; entry.exclude.length)) {&lt;br /&gt;
            var chips = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-chips&amp;#039; });&lt;br /&gt;
            if (entry.pages) {&lt;br /&gt;
                entry.pages.forEach(function (p) {&lt;br /&gt;
                    chips.appendChild(el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-chip&amp;#039; }, [&lt;br /&gt;
                        el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-chip-label&amp;#039;, textContent: &amp;#039;+&amp;#039; }),&lt;br /&gt;
                        p&lt;br /&gt;
                    ]));&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
            if (entry.exclude) {&lt;br /&gt;
                entry.exclude.forEach(function (p) {&lt;br /&gt;
                    chips.appendChild(el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-chip rb-ce-chip-exclude&amp;#039; }, [&lt;br /&gt;
                        el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-chip-label&amp;#039;, textContent: &amp;#039;\u2212&amp;#039; }),&lt;br /&gt;
                        p&lt;br /&gt;
                    ]));&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
            body.appendChild(chips);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var editBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-ghost rb-ce-btn-sm&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;Edit&amp;#039;,&lt;br /&gt;
            onClick: function () { editingIndex = idx; render(); }&lt;br /&gt;
        });&lt;br /&gt;
        var delBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-danger rb-ce-btn-sm&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;Del&amp;#039;,&lt;br /&gt;
            onClick: function () {&lt;br /&gt;
                if (confirm(&amp;#039;Delete this entry?&amp;#039;)) {&lt;br /&gt;
                    entries.splice(idx, 1);&lt;br /&gt;
                    if (editingIndex === idx) editingIndex = null;&lt;br /&gt;
                    markDirty();&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        var upBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-ghost rb-ce-btn-sm&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;\u25B2&amp;#039;,&lt;br /&gt;
            disabled: idx === 0 ? &amp;#039;disabled&amp;#039; : null,&lt;br /&gt;
            onClick: function () {&lt;br /&gt;
                var tmp = entries[idx - 1];&lt;br /&gt;
                entries[idx - 1] = entries[idx];&lt;br /&gt;
                entries[idx] = tmp;&lt;br /&gt;
                markDirty();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        var downBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-ghost rb-ce-btn-sm&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;\u25BC&amp;#039;,&lt;br /&gt;
            disabled: idx === entries.length - 1 ? &amp;#039;disabled&amp;#039; : null,&lt;br /&gt;
            onClick: function () {&lt;br /&gt;
                var tmp = entries[idx + 1];&lt;br /&gt;
                entries[idx + 1] = entries[idx];&lt;br /&gt;
                entries[idx] = tmp;&lt;br /&gt;
                markDirty();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        var actions = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-entry-actions&amp;#039; }, [upBtn, downBtn, editBtn, delBtn]);&lt;br /&gt;
&lt;br /&gt;
        return el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-entry&amp;#039; }, [meta, body, actions]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderForm() {&lt;br /&gt;
        var isNew = editingIndex === -1;&lt;br /&gt;
        var entry = isNew ? { id: &amp;#039;&amp;#039;, date: todayISO(), tag: &amp;#039;New&amp;#039;, text: &amp;#039;&amp;#039;, pages: [], exclude: [] } : JSON.parse(JSON.stringify(entries[editingIndex]));&lt;br /&gt;
        if (!entry.pages) entry.pages = [];&lt;br /&gt;
        if (!entry.exclude) entry.exclude = [];&lt;br /&gt;
        var autoId = isNew ? generateId(entry.date) : entry.id;&lt;br /&gt;
&lt;br /&gt;
        var form = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form&amp;#039; });&lt;br /&gt;
&lt;br /&gt;
        var dateInput = el(&amp;#039;input&amp;#039;, { type: &amp;#039;date&amp;#039;, value: entry.date });&lt;br /&gt;
        var tagSelect = el(&amp;#039;select&amp;#039;);&lt;br /&gt;
        VALID_TAGS.forEach(function (t) {&lt;br /&gt;
            var opt = el(&amp;#039;option&amp;#039;, { value: t, textContent: t });&lt;br /&gt;
            if (t === entry.tag) opt.selected = true;&lt;br /&gt;
            tagSelect.appendChild(opt);&lt;br /&gt;
        });&lt;br /&gt;
        var idInput = el(&amp;#039;input&amp;#039;, { type: &amp;#039;text&amp;#039;, value: isNew ? autoId : entry.id, placeholder: &amp;#039;Auto-generated&amp;#039; });&lt;br /&gt;
&lt;br /&gt;
        dateInput.addEventListener(&amp;#039;change&amp;#039;, function () {&lt;br /&gt;
            if (isNew) {&lt;br /&gt;
                entry.date = dateInput.value;&lt;br /&gt;
                idInput.value = generateId(dateInput.value);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        var row1 = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-row&amp;#039; }, [&lt;br /&gt;
            el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-field&amp;#039; }, [el(&amp;#039;label&amp;#039;, null, &amp;#039;Date&amp;#039;), dateInput]),&lt;br /&gt;
            el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-field&amp;#039; }, [el(&amp;#039;label&amp;#039;, null, &amp;#039;Tag&amp;#039;), tagSelect]),&lt;br /&gt;
            el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-field&amp;#039; }, [el(&amp;#039;label&amp;#039;, null, &amp;#039;ID&amp;#039;), idInput])&lt;br /&gt;
        ]);&lt;br /&gt;
&lt;br /&gt;
        var textArea = el(&amp;#039;textarea&amp;#039;, { placeholder: &amp;#039;Changelog text (wikitext supported)&amp;#039; });&lt;br /&gt;
        textArea.value = entry.text;&lt;br /&gt;
        var row2 = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-row&amp;#039; }, [&lt;br /&gt;
            el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-field&amp;#039;, style: &amp;#039;flex:1&amp;#039; }, [el(&amp;#039;label&amp;#039;, null, &amp;#039;Text&amp;#039;), textArea])&lt;br /&gt;
        ]);&lt;br /&gt;
&lt;br /&gt;
        var pagesChips = createChipInput(entry.pages);&lt;br /&gt;
        var excludeChips = createChipInput(entry.exclude);&lt;br /&gt;
        var row3 = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-row&amp;#039; }, [&lt;br /&gt;
            el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-field&amp;#039;, style: &amp;#039;flex:1&amp;#039; }, [el(&amp;#039;label&amp;#039;, null, &amp;#039;Pages (manual additions)&amp;#039;), pagesChips.element]),&lt;br /&gt;
            el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-field&amp;#039;, style: &amp;#039;flex:1&amp;#039; }, [el(&amp;#039;label&amp;#039;, null, &amp;#039;Exclude pages&amp;#039;), excludeChips.element])&lt;br /&gt;
        ]);&lt;br /&gt;
&lt;br /&gt;
        var saveFormBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-primary&amp;#039;,&lt;br /&gt;
            textContent: isNew ? &amp;#039;Add Entry&amp;#039; : &amp;#039;Update Entry&amp;#039;,&lt;br /&gt;
            onClick: function () {&lt;br /&gt;
                var newEntry = {&lt;br /&gt;
                    id: idInput.value.trim() || autoId,&lt;br /&gt;
                    date: dateInput.value,&lt;br /&gt;
                    tag: tagSelect.value,&lt;br /&gt;
                    text: textArea.value&lt;br /&gt;
                };&lt;br /&gt;
                var pages = pagesChips.getValues();&lt;br /&gt;
                var exclude = excludeChips.getValues();&lt;br /&gt;
                if (pages.length &amp;gt; 0) newEntry.pages = pages;&lt;br /&gt;
                if (exclude.length &amp;gt; 0) newEntry.exclude = exclude;&lt;br /&gt;
&lt;br /&gt;
                if (!newEntry.text.trim()) {&lt;br /&gt;
                    alert(&amp;#039;Text is required.&amp;#039;);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (isNew) {&lt;br /&gt;
                    entries.unshift(newEntry);&lt;br /&gt;
                } else {&lt;br /&gt;
                    entries[editingIndex] = newEntry;&lt;br /&gt;
                }&lt;br /&gt;
                editingIndex = null;&lt;br /&gt;
                markDirty();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        var cancelBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-ghost&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;Cancel&amp;#039;,&lt;br /&gt;
            onClick: function () { editingIndex = null; render(); }&lt;br /&gt;
        });&lt;br /&gt;
        var actions = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-form-actions&amp;#039; }, [saveFormBtn, cancelBtn]);&lt;br /&gt;
&lt;br /&gt;
        form.appendChild(row1);&lt;br /&gt;
        form.appendChild(row2);&lt;br /&gt;
        form.appendChild(row3);&lt;br /&gt;
        form.appendChild(actions);&lt;br /&gt;
        return form;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createChipInput(initialValues) {&lt;br /&gt;
        var values = (initialValues || []).slice();&lt;br /&gt;
        var wrap = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-chip-input-wrap&amp;#039; });&lt;br /&gt;
        var input = el(&amp;#039;input&amp;#039;, { type: &amp;#039;text&amp;#039;, placeholder: &amp;#039;Type page name...&amp;#039; });&lt;br /&gt;
        var dropdown = null;&lt;br /&gt;
        var debounceTimer = null;&lt;br /&gt;
        var activeIdx = -1;&lt;br /&gt;
&lt;br /&gt;
        function renderChips() {&lt;br /&gt;
            while (wrap.firstChild) wrap.removeChild(wrap.firstChild);&lt;br /&gt;
            values.forEach(function (v, i) {&lt;br /&gt;
                var removeBtn = el(&amp;#039;span&amp;#039;, {&lt;br /&gt;
                    className: &amp;#039;rb-ce-chip-remove&amp;#039;,&lt;br /&gt;
                    textContent: &amp;#039;\u00D7&amp;#039;,&lt;br /&gt;
                    onClick: function (e) {&lt;br /&gt;
                        e.stopPropagation();&lt;br /&gt;
                        values.splice(i, 1);&lt;br /&gt;
                        renderChips();&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                wrap.appendChild(el(&amp;#039;span&amp;#039;, { className: &amp;#039;rb-ce-chip&amp;#039; }, [v, removeBtn]));&lt;br /&gt;
            });&lt;br /&gt;
            wrap.appendChild(input);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function addChip(val) {&lt;br /&gt;
            var trimmed = val.trim();&lt;br /&gt;
            if (trimmed &amp;amp;&amp;amp; values.indexOf(trimmed) === -1) {&lt;br /&gt;
                values.push(trimmed);&lt;br /&gt;
            }&lt;br /&gt;
            input.value = &amp;#039;&amp;#039;;&lt;br /&gt;
            hideDropdown();&lt;br /&gt;
            renderChips();&lt;br /&gt;
            input.focus();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function showDropdown(items) {&lt;br /&gt;
            hideDropdown();&lt;br /&gt;
            if (items.length === 0) return;&lt;br /&gt;
            dropdown = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-autocomplete&amp;#039; });&lt;br /&gt;
            activeIdx = -1;&lt;br /&gt;
            items.forEach(function (item, i) {&lt;br /&gt;
                var div = el(&amp;#039;div&amp;#039;, {&lt;br /&gt;
                    className: &amp;#039;rb-ce-autocomplete-item&amp;#039;,&lt;br /&gt;
                    textContent: item,&lt;br /&gt;
                    onClick: function () { addChip(item); }&lt;br /&gt;
                });&lt;br /&gt;
                dropdown.appendChild(div);&lt;br /&gt;
            });&lt;br /&gt;
            wrap.appendChild(dropdown);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function hideDropdown() {&lt;br /&gt;
            if (dropdown &amp;amp;&amp;amp; dropdown.parentNode) {&lt;br /&gt;
                dropdown.parentNode.removeChild(dropdown);&lt;br /&gt;
            }&lt;br /&gt;
            dropdown = null;&lt;br /&gt;
            activeIdx = -1;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function highlightItem(idx) {&lt;br /&gt;
            if (!dropdown) return;&lt;br /&gt;
            var items = dropdown.querySelectorAll(&amp;#039;.rb-ce-autocomplete-item&amp;#039;);&lt;br /&gt;
            items.forEach(function (it, i) {&lt;br /&gt;
                it.classList.toggle(&amp;#039;rb-ce-ac-active&amp;#039;, i === idx);&lt;br /&gt;
            });&lt;br /&gt;
            activeIdx = idx;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&amp;#039;input&amp;#039;, function () {&lt;br /&gt;
            clearTimeout(debounceTimer);&lt;br /&gt;
            var q = input.value;&lt;br /&gt;
            if (q.length &amp;lt; 2) { hideDropdown(); return; }&lt;br /&gt;
            debounceTimer = setTimeout(function () {&lt;br /&gt;
                var results = searchPageTitles(q);&lt;br /&gt;
                showDropdown(results);&lt;br /&gt;
            }, 300);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&amp;#039;keydown&amp;#039;, function (e) {&lt;br /&gt;
            if (e.key === &amp;#039;,&amp;#039; || e.key === &amp;#039;Enter&amp;#039;) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                if (activeIdx &amp;gt;= 0 &amp;amp;&amp;amp; dropdown) {&lt;br /&gt;
                    var items = dropdown.querySelectorAll(&amp;#039;.rb-ce-autocomplete-item&amp;#039;);&lt;br /&gt;
                    if (items[activeIdx]) addChip(items[activeIdx].textContent);&lt;br /&gt;
                } else if (input.value.trim()) {&lt;br /&gt;
                    addChip(input.value);&lt;br /&gt;
                }&lt;br /&gt;
            } else if (e.key === &amp;#039;ArrowDown&amp;#039; &amp;amp;&amp;amp; dropdown) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                var items = dropdown.querySelectorAll(&amp;#039;.rb-ce-autocomplete-item&amp;#039;);&lt;br /&gt;
                highlightItem(Math.min(activeIdx + 1, items.length - 1));&lt;br /&gt;
            } else if (e.key === &amp;#039;ArrowUp&amp;#039; &amp;amp;&amp;amp; dropdown) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                highlightItem(Math.max(activeIdx - 1, 0));&lt;br /&gt;
            } else if (e.key === &amp;#039;Escape&amp;#039;) {&lt;br /&gt;
                hideDropdown();&lt;br /&gt;
            } else if (e.key === &amp;#039;Backspace&amp;#039; &amp;amp;&amp;amp; input.value === &amp;#039;&amp;#039; &amp;amp;&amp;amp; values.length &amp;gt; 0) {&lt;br /&gt;
                values.pop();&lt;br /&gt;
                renderChips();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&amp;#039;blur&amp;#039;, function () {&lt;br /&gt;
            setTimeout(hideDropdown, 200);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        wrap.addEventListener(&amp;#039;click&amp;#039;, function () { input.focus(); });&lt;br /&gt;
&lt;br /&gt;
        renderChips();&lt;br /&gt;
        return { element: wrap, getValues: function () { return values.slice(); } };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPagination() {&lt;br /&gt;
        var totalPages = Math.max(1, Math.ceil(entries.length / PER_PAGE));&lt;br /&gt;
        var info = el(&amp;#039;span&amp;#039;, null, &amp;#039;Page &amp;#039; + (currentPage + 1) + &amp;#039; of &amp;#039; + totalPages + &amp;#039; (&amp;#039; + entries.length + &amp;#039; entries)&amp;#039;);&lt;br /&gt;
        var prevBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-ghost rb-ce-btn-sm&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;\u25C0 Prev&amp;#039;,&lt;br /&gt;
            disabled: currentPage === 0 ? &amp;#039;disabled&amp;#039; : null,&lt;br /&gt;
            onClick: function () { currentPage--; editingIndex = null; render(); }&lt;br /&gt;
        });&lt;br /&gt;
        var nextBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-ghost rb-ce-btn-sm&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;Next \u25B6&amp;#039;,&lt;br /&gt;
            disabled: currentPage &amp;gt;= totalPages - 1 ? &amp;#039;disabled&amp;#039; : null,&lt;br /&gt;
            onClick: function () { currentPage++; editingIndex = null; render(); }&lt;br /&gt;
        });&lt;br /&gt;
        return el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-pagination&amp;#039; }, [prevBtn, info, nextBtn]);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showConfirmDialog() {&lt;br /&gt;
        var summary = computeChangeSummary();&lt;br /&gt;
        var lines = [];&lt;br /&gt;
        if (summary.added.length) lines.push(summary.added.length + &amp;#039; added&amp;#039;);&lt;br /&gt;
        if (summary.modified.length) lines.push(summary.modified.length + &amp;#039; modified&amp;#039;);&lt;br /&gt;
        if (summary.deleted.length) lines.push(summary.deleted.length + &amp;#039; deleted&amp;#039;);&lt;br /&gt;
        if (summary.reordered) lines.push(&amp;#039;entries reordered&amp;#039;);&lt;br /&gt;
        var changeText = lines.join(&amp;#039;, &amp;#039;) || &amp;#039;No detectable changes&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
        var overlay = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-overlay&amp;#039; });&lt;br /&gt;
        var dialog = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-dialog&amp;#039; });&lt;br /&gt;
&lt;br /&gt;
        dialog.appendChild(el(&amp;#039;h3&amp;#039;, null, &amp;#039;Confirm Save&amp;#039;));&lt;br /&gt;
&lt;br /&gt;
        var summaryDiv = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-dialog-summary&amp;#039; });&lt;br /&gt;
        summaryDiv.appendChild(el(&amp;#039;p&amp;#039;, null, &amp;#039;Changes: &amp;#039; + changeText));&lt;br /&gt;
        if (summary.added.length) {&lt;br /&gt;
            var addedList = el(&amp;#039;ul&amp;#039;);&lt;br /&gt;
            summary.added.forEach(function (e) {&lt;br /&gt;
                addedList.appendChild(el(&amp;#039;li&amp;#039;, null, &amp;#039;+ [&amp;#039; + e.tag + &amp;#039;] &amp;#039; + e.text.substring(0, 80) + (e.text.length &amp;gt; 80 ? &amp;#039;...&amp;#039; : &amp;#039;&amp;#039;)));&lt;br /&gt;
            });&lt;br /&gt;
            summaryDiv.appendChild(addedList);&lt;br /&gt;
        }&lt;br /&gt;
        if (summary.deleted.length) {&lt;br /&gt;
            var delList = el(&amp;#039;ul&amp;#039;);&lt;br /&gt;
            summary.deleted.forEach(function (e) {&lt;br /&gt;
                delList.appendChild(el(&amp;#039;li&amp;#039;, null, &amp;#039;\u2212 [&amp;#039; + e.tag + &amp;#039;] &amp;#039; + e.text.substring(0, 80) + (e.text.length &amp;gt; 80 ? &amp;#039;...&amp;#039; : &amp;#039;&amp;#039;)));&lt;br /&gt;
            });&lt;br /&gt;
            summaryDiv.appendChild(delList);&lt;br /&gt;
        }&lt;br /&gt;
        dialog.appendChild(summaryDiv);&lt;br /&gt;
&lt;br /&gt;
        var defaultSummary = &amp;#039;Changelog: &amp;#039; + changeText;&lt;br /&gt;
        var summaryField = el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-dialog-field&amp;#039; });&lt;br /&gt;
        summaryField.appendChild(el(&amp;#039;label&amp;#039;, null, &amp;#039;Edit summary&amp;#039;));&lt;br /&gt;
        var summaryInput = el(&amp;#039;input&amp;#039;, { type: &amp;#039;text&amp;#039;, value: defaultSummary });&lt;br /&gt;
        summaryField.appendChild(summaryInput);&lt;br /&gt;
        dialog.appendChild(summaryField);&lt;br /&gt;
&lt;br /&gt;
        var saving = false;&lt;br /&gt;
        var saveBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-primary&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;Save&amp;#039;,&lt;br /&gt;
            onClick: function () {&lt;br /&gt;
                if (saving) return;&lt;br /&gt;
                saving = true;&lt;br /&gt;
                saveBtn.disabled = true;&lt;br /&gt;
                saveBtn.textContent = &amp;#039;Saving...&amp;#039;;&lt;br /&gt;
                saveData(summaryInput.value || defaultSummary).then(function () {&lt;br /&gt;
                    overlay.parentNode.removeChild(overlay);&lt;br /&gt;
                    render();&lt;br /&gt;
                }).catch(function (err) {&lt;br /&gt;
                    saving = false;&lt;br /&gt;
                    saveBtn.disabled = false;&lt;br /&gt;
                    saveBtn.textContent = &amp;#039;Save&amp;#039;;&lt;br /&gt;
                    try {&lt;br /&gt;
                        sessionStorage.setItem(&amp;#039;rb-ce-backup&amp;#039;, JSON.stringify(entries));&lt;br /&gt;
                    } catch (e) {}&lt;br /&gt;
                    alert(&amp;#039;Save failed: &amp;#039; + err.message + &amp;#039;\n\nYour changes have been backed up to session storage.&amp;#039;);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        var cancelBtn = el(&amp;#039;button&amp;#039;, {&lt;br /&gt;
            className: &amp;#039;rb-ce-btn rb-ce-btn-ghost&amp;#039;,&lt;br /&gt;
            textContent: &amp;#039;Cancel&amp;#039;,&lt;br /&gt;
            onClick: function () { overlay.parentNode.removeChild(overlay); }&lt;br /&gt;
        });&lt;br /&gt;
        dialog.appendChild(el(&amp;#039;div&amp;#039;, { className: &amp;#039;rb-ce-dialog-actions&amp;#039; }, [cancelBtn, saveBtn]));&lt;br /&gt;
&lt;br /&gt;
        overlay.appendChild(dialog);&lt;br /&gt;
        overlay.addEventListener(&amp;#039;click&amp;#039;, function (e) {&lt;br /&gt;
            if (e.target === overlay) overlay.parentNode.removeChild(overlay);&lt;br /&gt;
        });&lt;br /&gt;
        document.body.appendChild(overlay);&lt;br /&gt;
        summaryInput.focus();&lt;br /&gt;
        summaryInput.select();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function init() {&lt;br /&gt;
        root.innerHTML = &amp;#039;&amp;lt;div class=&amp;quot;rb-ce-status&amp;quot;&amp;gt;Loading changelog data...&amp;lt;/div&amp;gt;&amp;#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            var backup = sessionStorage.getItem(&amp;#039;rb-ce-backup&amp;#039;);&lt;br /&gt;
            if (backup) {&lt;br /&gt;
                if (confirm(&amp;#039;A backup from a previous unsaved session was found. Restore it?&amp;#039;)) {&lt;br /&gt;
                    entries = JSON.parse(backup);&lt;br /&gt;
                    dirty = true;&lt;br /&gt;
                    sessionStorage.removeItem(&amp;#039;rb-ce-backup&amp;#039;);&lt;br /&gt;
                    Promise.all([fetchEditToken(), fetchAllPageTitles()]).then(function () {&lt;br /&gt;
                        render();&lt;br /&gt;
                    });&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                sessionStorage.removeItem(&amp;#039;rb-ce-backup&amp;#039;);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (e) {}&lt;br /&gt;
&lt;br /&gt;
        Promise.all([fetchData(), fetchEditToken(), fetchAllPageTitles()])&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                render();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function (err) {&lt;br /&gt;
                root.innerHTML = &amp;#039;&amp;lt;div class=&amp;quot;rb-ce-error&amp;quot;&amp;gt;Failed to load: &amp;#039; + err.message + &amp;#039;&amp;lt;/div&amp;gt;&amp;#039;;&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &amp;#039;loading&amp;#039;) {&lt;br /&gt;
        document.addEventListener(&amp;#039;DOMContentLoaded&amp;#039;, init);&lt;br /&gt;
    } else {&lt;br /&gt;
        init();&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;/div&gt;</summary>
		<author><name>East6</name></author>
	</entry>
</feed>