(function(F){ "use strict"; const E = (s)=>document.querySelector(s), D = F.dom, P = F.page; P.config = { defaultMaxStashSize: 10, useConfirmerButtons:{ save: false, reload: true, discardStash: true } }; const $stash = { keys: { index: F.page.name+'.index' }, indexKey: function(winfo){return winfo.name}, contentKey: function(suffix){return P.name+'/'+suffix}, getIndex: function(){ if(!this.index){ this.index = F.storage.getJSON( this.keys.index, {} ); } return this.index; }, _fireStashEvent: function(){ if(this._disableNextEvent) delete this._disableNextEvent; else F.page.dispatchEvent('wiki-stash-updated', this); }, getWinfo: function(winfo){ const ndx = this.getIndex(); return ndx[this.indexKey(winfo)]; }, storeIndex: function(){ if(this.index) F.storage.setJSON(this.keys.index,this.index); return this; }, updateWinfo: function(winfo,content){ const ndx = this.getIndex(), key = this.indexKey(winfo), old = ndx[key]; const record = old || (ndx[key]={ name: winfo.name }); record.mimetype = winfo.mimetype; record.type = winfo.type; record.parent = winfo.parent; record.version = winfo.version; record.stashTime = new Date().getTime(); record.isEmpty = !!winfo.isEmpty; record.attachments = winfo.attachments; this.storeIndex(); if(arguments.length>1){ if(content) delete record.isEmpty; F.storage.set(this.contentKey(key), content); } this._fireStashEvent(); return this; }, stashedContent: function(winfo){ return F.storage.get(this.contentKey(this.indexKey(winfo))); }, hasStashedContent: function(winfo){ if('string'===typeof winfo) winfo = {name: winfo}; return F.storage.contains(this.contentKey(this.indexKey(winfo))); }, unstash: function(winfo){ const ndx = this.getIndex(), key = this.indexKey(winfo); delete winfo.stashTime; delete ndx[key]; F.storage.remove(this.contentKey(key)); this.storeIndex(); this._fireStashEvent(); return this; }, clear: function(){ const ndx = this.getIndex(), self = this; let count = 0; Object.keys(ndx).forEach(function(k){ ++count; const e = ndx[k]; delete ndx[k]; F.storage.remove(self.contentKey(k)); }); F.storage.remove(this.keys.index); delete this.index; if(count) this._fireStashEvent(); return this; }, prune: function f(maxCount){ const ndx = this.getIndex(); const li = []; if(!maxCount || maxCount<0) maxCount = f.defaultMaxCount; Object.keys(ndx).forEach((k)=>li.push(ndx[k])); li.sort((l,r)=>l.stashTime - r.stashTime); let n = 0; while(li.length>maxCount){ ++n; const e = li.shift(); this._disableNextEvent = true; this.unstash(e); console.warn("Pruned oldest local file edit entry:",e); } if(n) this._fireStashEvent(); } }; $stash.prune.defaultMaxCount = P.config.defaultMaxStashSize || 10; P.$stash = $stash; P.selectMimetype = function(modeValue, forceEvent){ const s = this.e.selectMimetype; if(!modeValue) modeValue = s.value; else if(s.value != modeValue){ s.value = modeValue; forceEvent = true; } if(forceEvent){ s.dispatchEvent(new Event('change',{target:s})); } }; const getEditMarker = function f(winfo, textOnly){ const esm = F.config.editStateMarkers; if(f.NEW===winfo){ return textOnly ? esm.isNew : D.addClass(D.append(D.span(),esm.isNew), 'is-new'); }else if(f.MODIFIED===winfo){ return textOnly ? esm.isModified : D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); }else if(f.DELETED===winfo){ return textOnly ? esm.isDeleted : D.addClass(D.append(D.span(),esm.isDeleted), 'is-deleted'); }else if(winfo && winfo.version){ if($stash.getWinfo(winfo)){ return textOnly ? esm.isModified : D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); } } else if(winfo){ if('sandbox'!==winfo.type){ return textOnly ? esm.isNew : D.addClass(D.append(D.span(),esm.isNew), 'is-new'); }else if($stash.getWinfo(winfo)){ return textOnly ? esm.isModified : D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); } } return textOnly ? '' : D.span(); }; getEditMarker.NEW = 1; getEditMarker.MODIFIED = 2; getEditMarker.DELETED = 3; const winfoIsNew = function(winfo){ if(!winfo) return undefined; else if('sandbox' === winfo.type) return false; else return !winfo.version; }; const WikiList = { e: { filterCheckboxes: { }, }, cache: { pageList: [], optByName:{}, names: { } }, _refreshStashMarks: function callee(option){ if(!callee.eachOpt){ const self = this; callee.eachOpt = function(keyOrOpt){ const opt = 'string'===typeof keyOrOpt ? self.e.select.options[keyOrOpt] : keyOrOpt; const stashed = $stash.getWinfo({name:opt.value}); var prefix = ''; D.removeClass(opt, 'stashed', 'stashed-new', 'deleted'); if(stashed){ const isNew = winfoIsNew(stashed); prefix = getEditMarker(isNew ? getEditMarker.NEW : getEditMarker.MODIFIED, true); D.addClass(opt, isNew ? 'stashed-new' : 'stashed'); D.removeClass(opt, 'deleted'); }else if(opt.dataset.isDeleted){ prefix = getEditMarker(getEditMarker.DELETED,true); D.addClass(opt, 'deleted'); } opt.innerText = prefix + opt.value; self.cache.names[opt.value] = true; }; } if(arguments.length){ callee.eachOpt(option); }else{ this.cache.names = {}; Object.keys(this.e.select.options).forEach(callee.eachOpt); } }, removeEntry: function(name){ const sel = this.e.select; var ndx = sel.selectedIndex; sel.value = name; if(sel.selectedIndex>-1){ if(ndx === sel.selectedIndex) ndx = -1; sel.options.remove(sel.selectedIndex); } sel.selectedIndex = ndx; delete this.cache.names[name]; delete this.cache.optByName[name]; this.cache.pageList = this.cache.pageList.filter((wi)=>name !== wi.name); }, _rebuildList: function callee(){ const list = this.cache.pageList; if(!list) return; if(!callee.sorticase){ callee.sorticase = function(l,r){ if(l===r) return 0; l = l.toLowerCase(); r = r.toLowerCase(); return l<=r ? -1 : 1; }; } const map = {}, ndx = $stash.getIndex(), sel = this.e.select; D.clearElement(sel); list.forEach((winfo)=>map[winfo.name] = winfo); Object.keys(ndx).forEach(function(key){ const winfo = ndx[key]; if(!winfo.version) map[winfo.name] = winfo; }); const self = this; Object.keys(map) .sort(callee.sorticase) .forEach(function(name){ const winfo = map[name]; const opt = D.option(sel, winfo.name); const wtype = opt.dataset.wtype = winfo.type==='sandbox' ? 'normal' : (winfo.type||'normal'); const cb = self.e.filterCheckboxes[wtype]; self.cache.optByName[winfo.name] = opt; if(cb && !cb.checked) D.addClass(opt, 'hidden'); if(winfo.isEmpty){ opt.dataset.isDeleted = true; } self._refreshStashMarks(opt); }); D.enable(sel); if(P.winfo) sel.value = P.winfo.name; }, loadList: function callee(){ if(!callee.onload){ const self = this; callee.onload = function(list){ self.cache.pageList = list; self._rebuildList(); F.message("Loaded page list."); }; } if(P.initialPageList){ const list = P.initialPageList; delete P.initialPageList; callee.onload(list); }else{ F.fetch('wikiajax/list',{ urlParams:{verbose:true}, responseType: 'json', onload: callee.onload }); } return this; }, validatePageName: function(name){ var err; if(!name){ err = "may not be empty"; }else if(this.cache.names.hasOwnProperty(name)){ err = "page already exists: "+name; }else if(name.length>100){ err = "too long (limit is 100)"; }else if(/\s{2,}/.test(name)){ err = "multiple consecutive spaces"; }else if(/[\t\r\n]/.test(name)){ err = "contains control character(s)"; }else{ let i = 0, n = name.length, c; for( ; i < n; ++i ){ if(name.charCodeAt(i)<0x20){ err = "contains control character(s)"; break; } } } if(err){ F.error("Invalid name:",err); } return !err; }, addNewPage: function(name){ name = name.trim(); if(!this.validatePageName(name)) return false; var wtype = 'normal'; if(0===name.indexOf('checkin/')) wtype = 'checkin'; else if(0===name.indexOf('branch/')) wtype = 'branch'; else if(0===name.indexOf('tag/')) wtype = 'tag'; const winfo = { name: name, type: wtype, mimetype: 'text/x-markdown', version: null, parent: null }; this.cache.pageList.push( winfo ); $stash.updateWinfo(winfo, ''); this._rebuildList(); P.loadPage(winfo.name); return true; }, init: function(parentElem){ const sel = D.select(), btn = D.addClass(D.button("Reload page list"), 'save'); this.e.select = sel; D.addClass(parentElem, 'WikiList'); D.clearElement(parentElem); D.append( parentElem, D.append(D.fieldset("Select a page to edit"), sel) ); D.attr(sel, 'size', 12); D.option(D.disable(D.clearElement(sel)), undefined, "Loading..."); const fsFilter = D.addClass(D.fieldset("Page types"),"page-types-list"), fsFilterBody = D.div(), filters = ['normal', 'branch/...', 'tag/...', 'checkin/...'] ; D.append(fsFilter, fsFilterBody); D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch'); const self = this; const filterByType = function(wtype, show){ sel.querySelectorAll('option[data-wtype='+wtype+']').forEach(function(opt){ if(show) opt.classList.remove('hidden'); else opt.classList.add('hidden'); }); }; filters.forEach(function(label){ const wtype = label.split('/')[0]; const cbId = 'wtype-filter-'+wtype, lbl = D.attr(D.append(D.label(),label), 'for', cbId), cb = D.attr(D.input('checkbox'), 'id', cbId); D.append(fsFilterBody, D.append(D.span(), cb, lbl)); self.e.filterCheckboxes[wtype] = cb; cb.checked = true; filterByType(wtype, cb.checked); cb.addEventListener( 'change', function(ev){filterByType(wtype, ev.target.checked)}, false ); }); { const cbId = 'wtype-filter-deleted', lbl = D.attr(D.append(D.label(), getEditMarker(getEditMarker.DELETED,false), 'deleted'), 'for', cbId), cb = D.attr(D.input('checkbox'), 'id', cbId); cb.checked = false; D.addClass(parentElem,'hide-deleted'); D.attr(lbl); const deletedTip = F.helpButtonlets.create( D.span(), 'Fossil considers empty pages to be "deleted" in some contexts.' ); D.append(fsFilterBody, D.append( D.span(), cb, lbl, deletedTip )); cb.addEventListener( 'change', function(ev){ if(ev.target.checked) D.removeClass(parentElem,'hide-deleted'); else D.addClass(parentElem,'hide-deleted'); }, false); } const fsLegend = D.fieldset("Edit status"), fsLegendBody = D.div(); D.append(fsLegend, fsLegendBody); D.addClass(fsLegendBody, 'flex-container', 'flex-column', 'stretch'); D.append( fsLegendBody, D.append(D.span(), getEditMarker(getEditMarker.NEW,false)," = new/unsaved"), D.append(D.span(), getEditMarker(getEditMarker.MODIFIED,false)," = has local edits"), D.append(D.span(), getEditMarker(getEditMarker.DELETED,false)," = is empty (deleted)") ); const fsNewPage = D.fieldset("Create new page"), fsNewPageBody = D.div(), newPageName = D.input('text'), newPageBtn = D.button("Add page locally") ; D.append(parentElem, fsNewPage); D.append(fsNewPage, fsNewPageBody); D.addClass(fsNewPageBody, 'flex-container', 'flex-column', 'new-page'); D.append( fsNewPageBody, newPageName, newPageBtn, D.append(D.addClass(D.span(), 'mini-tip'), "New pages exist only in this browser until they are saved.") ); newPageBtn.addEventListener('click', function(){ if(self.addNewPage(newPageName.value)){ newPageName.value = ''; } }, false); D.append( parentElem, D.append(D.addClass(D.div(), 'fieldset-wrapper'), fsFilter, fsNewPage, fsLegend) ); D.append(parentElem, btn); btn.addEventListener('click', ()=>this.loadList(), false); this.loadList(); const onSelect = (e)=>P.loadPage(e.target.value); sel.addEventListener('change', onSelect, false); sel.addEventListener('dblclick', onSelect, false); F.page.addEventListener('wiki-stash-updated', ()=>{ if(P.winfo) this._refreshStashMarks(); else this._rebuildList(); }); F.page.addEventListener('wiki-page-loaded', function(ev){ const page = ev.detail, opt = self.cache.optByName[page.name]; if(opt){ if(page.isEmpty) opt.dataset.isDeleted = true; else delete opt.dataset.isDeleted; self._refreshStashMarks(opt); }else if('sandbox'!==page.type){ F.error("BUG: internal mis-handling of page object: missing OPTION for page "+page.name); } }); delete this.init; } }; P.stashWidget = { e:{}, init: function(domInsertPoint){ const wrapper = D.addClass( D.attr(D.div(),'id','wikiedit-stash-selector'), 'input-with-label' ); const sel = this.e.select = D.select(), btnClear = this.e.btnClear = D.button("Discard Edits"), btnHelp = D.append( D.addClass(D.div(), "help-buttonlet"), 'Locally-edited wiki pages. Timestamps are the last local edit time. ', 'Only the ',P.config.defaultMaxStashSize,' most recent pages ', 'are retained. Saving or reloading a file removes it from this list. ', D.append(D.code(),F.storage.storageImplName()), ' = ',F.storage.storageHelpDescription() ); D.append(wrapper, "Local edits (", D.append(D.code(), F.storage.storageImplName()), "):", btnHelp, sel, btnClear); F.helpButtonlets.setup(btnHelp); D.option(D.disable(sel), undefined, "(empty)"); P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail)); P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail)); sel.addEventListener('change',function(e){ const opt = this.selectedOptions[0]; if(opt && opt._winfo) P.loadPage(opt._winfo); }); if(F.storage.isTransient()){ D.append(wrapper, D.append( D.addClass(D.span(),'warning'), "Warning: persistent storage is not available, "+ "so uncomitted edits will not survive a page reload." )); } domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint); if(P.config.useConfirmerButtons.discardStash){ F.confirmer(btnClear, { pinSize: true, confirmText: "DISCARD all local edits?", onconfirm: ()=>P.clearStash(), ticks: F.config.confirmerButtonTicks }); }else{ btnClear.addEventListener('click', ()=>P.clearStash(), false); } D.addClass(btnClear,'hidden'); $stash._fireStashEvent(); delete this.init; }, updateList: function f(stasher,theWinfo){ if(!f.compare){ const cmpBase = (l,r)=>lcmpBase(l.name.toLowerCase(), r.name.toLowerCase()); f.rxZ = /\.\d+Z$/; const pad=(x)=>(''+x).length>1 ? x : '0'+x; f.timestring = function(d){ return [ d.getFullYear(),'-',pad(d.getMonth()+1),'-',pad(d.getDate()), '@',pad(d.getHours()),':',pad(d.getMinutes()) ].join(''); }; } const index = stasher.getIndex(), ilist = []; Object.keys(index).forEach((winfo)=>{ ilist.push(index[winfo]); }); const self = this; D.clearElement(this.e.select); if(0===ilist.length){ D.addClass(this.e.btnClear, 'hidden'); D.option(D.disable(this.e.select),undefined,"No local edits"); return; } D.enable(this.e.select); if(true){ D.removeClass(this.e.btnClear, 'hidden'); } D.disable(D.option(this.e.select,undefined,"Select a local edit...")); const currentWinfo = theWinfo || P.winfo || {name:''}; ilist.sort(f.compare).forEach(function(winfo,n){ const key = stasher.indexKey(winfo), rev = winfo.version || ''; const opt = D.option( self.e.select, n+1, [winfo.name, ' [', rev ? F.hashDigits(rev) : ( winfo.type==='sandbox' ? 'sandbox' : 'new/local' ),'] ', f.timestring(new Date(winfo.stashTime)) ].join('') ); opt._winfo = winfo; if(0===f.compare(currentWinfo, winfo)){ D.attr(opt, 'selected', true); } }); } }; const ajaxState = { count: 0, toDisable: undefined }; F.fetch.beforesend = function f(){ if(!ajaxState.toDisable){ ajaxState.toDisable = document.querySelectorAll( ['button:not([disabled])', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'fieldset:not([disabled])' ].join(',') ); } if(1===++ajaxState.count){ D.addClass(document.body, 'waiting'); D.disable(ajaxState.toDisable); } }; F.fetch.aftersend = function(){ if(0===--ajaxState.count){ D.removeClass(document.body, 'waiting'); D.enable(ajaxState.toDisable); delete ajaxState.toDisable; } }; F.onPageLoad(function() { document.body.classList.add('wikiedit'); P.base = {tag: E('base'), wikiUrl: F.repoUrl('wiki')}; P.base.originalHref = P.base.tag.href; P.e = { taEditor: E('#wikiedit-content-editor'), btnReload: E("#wikiedit-tab-content button.wikiedit-content-reload"), btnSave: E("button.wikiedit-save"), btnSaveClose: E("button.wikiedit-save-close"), selectMimetype: E('select[name=mimetype]'), selectFontSizeWrap: E('#select-font-size'), cbAutoPreview: E('#cb-preview-autorefresh'), previewTarget: E('#wikiedit-tab-preview-wrapper'), diffTarget: E('#wikiedit-tab-diff-wrapper'), editStatus: E('#wikiedit-edit-status'), tabContainer: E('#wikiedit-tabs'), attachmentContainer: E("#attachment-wrapper"), tabs:{ pageList: E('#wikiedit-tab-pages'), content: E('#wikiedit-tab-content'), preview: E('#wikiedit-tab-preview'), diff: E('#wikiedit-tab-diff'), misc: E('#wikiedit-tab-misc') } }; P.tabs = new F.TabManager(D.clearElement(P.e.tabContainer)); P.tabs.addCustomWidget( E('#fossil-status-bar') ).addCustomWidget(P.e.editStatus); let currentTab; P.tabs.addEventListener( 'before-switch-to', function(ev){ const theTab = currentTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot'); if(btnSlot){ btnSlot.parentNode.insertBefore( P.e.btnSave.parentNode, btnSlot ); btnSlot.parentNode.insertBefore( P.e.btnSaveClose.parentNode, btnSlot ); P.updateSaveButton(); } if(theTab===P.e.tabs.preview){ P.baseHrefForWiki(); if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview(); }else if(theTab===P.e.tabs.diff){ D.removeClass(P.e.diffTarget, 'hidden'); } } ); P.tabs.addEventListener( 'before-switch-from', function(ev){ const theTab = ev.detail; if(theTab===P.e.tabs.preview){ P.baseHrefRestore(); }else if(theTab===P.e.tabs.diff){ D.addClass(P.e.diffTarget, 'hidden'); } } ); P.e.taEditor.addEventListener('keydown',function(ev){ if(ev.shiftKey && 13 === ev.keyCode){ ev.preventDefault(); ev.stopPropagation(); P.e.taEditor.blur(); P.tabs.switchToTab(P.e.tabs.preview); if(!P.e.cbAutoPreview.checked){ P.preview(); } } }, false); document.body.addEventListener('keydown',function(ev){ if(ev.shiftKey && 13 === ev.keyCode){ if(currentTab === P.e.tabs.preview){ ev.preventDefault(); ev.stopPropagation(); P.tabs.switchToTab(P.e.tabs.content); P.e.taEditor.focus(); return false; } } }, true); F.connectPagePreviewers( P.e.tabs.preview.querySelector( '#btn-preview-refresh' ) ); const diffButtons = E('#wikiedit-tab-diff-buttons'); diffButtons.querySelector('button.sbs').addEventListener( "click",(e)=>P.diff(true), false ); diffButtons.querySelector('button.unified').addEventListener( "click",(e)=>P.diff(false), false ); if(0) P.e.btnCommit.addEventListener( "click",(e)=>P.commit(), false ); const doSave = function(alsoClose){ const w = P.winfo; if(!w){ F.error("No page loaded."); return; } if(alsoClose){ P.save(()=>window.location.href=F.repoUrl('wiki',{name: w.name})); }else{ P.save(); } }; const doReload = function(e){ const w = P.winfo; if(!w){ F.error("No page loaded."); return; } if(!w.version && w.type!=='sandbox' && P.wikiContent()){ F.error("This new/unsaved page has content.", "To really discard this page,", "first clear its content", "then use the Discard button."); return; } P.unstashContent(); if(w.version || w.type==='sandbox'){ P.loadPage(w); }else{ WikiList.removeEntry(w.name); delete P.winfo; P.updatePageTitle(); F.message("Discarded new page ["+w.name+"]."); } }; if(P.config.useConfirmerButtons.reload){ P.tabs.switchToTab(1); F.confirmer(P.e.btnReload, { pinSize: true, confirmText: "Really reload, losing edits?", onconfirm: doReload, ticks: F.config.confirmerButtonTicks }); }else{ P.e.btnReload.addEventListener('click', doReload, false); } if(P.config.useConfirmerButtons.save){ P.tabs.switchToTab(1); F.confirmer(P.e.btnSave, { pinSize: true, confirmText: "Really save changes?", onconfirm: ()=>doSave(), ticks: F.config.confirmerButtonTicks }); F.confirmer(P.e.btnSaveClose, { pinSize: true, confirmText: "Really save changes?", onconfirm: ()=>doSave(true), ticks: F.config.confirmerButtonTicks }); }else{ P.e.btnSave.addEventListener('click', ()=>doSave(), false); P.e.btnSaveClose.addEventListener('click', ()=>doSave(true), false); } P.e.taEditor.addEventListener('change', ()=>P.notifyOfChange(), false); P.selectMimetype(false, true); P.e.selectMimetype.addEventListener( 'change', function(e){ if(P.winfo && P.winfo.mimetype !== e.target.value){ P.winfo.mimetype = e.target.value; P._isDirty = true; P.stashContentChange(true); } }, false ); const selectFontSize = E('select[name=editor_font_size]'); if(selectFontSize){ selectFontSize.addEventListener( "change",function(e){ const ed = P.e.taEditor; ed.className = ed.className.replace( /\bfont-size-\d+/g, '' ); ed.classList.add('font-size-'+e.target.value); }, false ); selectFontSize.dispatchEvent( new Event('change',{target:selectFontSize}) ); } P.addEventListener( 'wiki-content-replaced', ()=>{ P.previewNeedsUpdate = true; D.clearElement(P.e.diffTarget, P.e.previewTarget); } ); P.addEventListener( 'wiki-saved', (e)=>{ D.clearElement(P.e.diffTarget, P.e.previewTarget); } ); P.addEventListener('wiki-stash-updated',function(){ if(P.winfo && !P.winfo.version && !$stash.getWinfo(P.winfo)){ delete P.winfo; P.wikiContent(''); P.updatePageTitle(); } P.updateSaveButton(); }).updatePageTitle().updateSaveButton(); P.addEventListener( 'wiki-page-loaded', function(ev){ delete P._isDirty; const winfo = ev.detail; P.winfo = winfo; P.previewNeedsUpdate = true; P.e.selectMimetype.value = winfo.mimetype; P.tabs.switchToTab(P.e.tabs.content); P.wikiContent(winfo.content || ''); WikiList.e.select.value = winfo.name; if(!winfo.version && winfo.type!=='sandbox'){ F.message('You are editing a new, unsaved page:',winfo.name); } P.updatePageTitle().updateSaveButton(); }, false ); P.tabs.switchToTab(0); WikiList.init( P.e.tabs.pageList.firstElementChild ); P.tabs.switchToTab(1); P.stashWidget.init(P.e.tabs.content.lastElementChild); P.tabs.switchToTab(0); }); const affirmPageLoaded = function(quiet){ if(!P.winfo && !quiet) F.error("No wiki page is loaded."); return !!P.winfo; }; P.updateAttachmentsView = function f(){ if(!f.eAttach){ f.eAttach = P.e.attachmentContainer.querySelector('div'); } D.clearElement(f.eAttach); const wi = this.winfo; if(!wi){ D.append(f.eAttach,"No page loaded."); return this; } else if(!wi.version){ D.append(f.eAttach, "Page ["+wi.name+"] cannot have ", "attachments until it is saved once."); return this; } const btnReload = D.button("Reload list"); const self = this; btnReload.addEventListener('click', function(){ const isStashed = $stash.hasStashedContent(wi); F.fetch('wikiajax/attachments',{ responseType: 'json', urlParams: {page: wi.name}, onload: function(r){ wi.attachments = r; if(isStashed) self.stashContentChange(true); F.message("Reloaded attachment list for ["+wi.name+"]."); self.updateAttachmentsView(); } }); }); if(!wi.attachments || !wi.attachments.length){ D.append(f.eAttach, btnReload, " No attachments found for page ["+wi.name+"]. ", D.a(F.repoUrl('attachadd',{ page: wi.name, from: F.repoUrl('wikiedit',{name: wi.name})}), "Add attachments..." ) ); return this; } D.append( f.eAttach, D.append(D.p(), btnReload," ", D.a(F.repoUrl('attachlist',{page:wi.name}), "Attachments for page ["+wi.name+"]."), " ", D.a(F.repoUrl('attachadd',{ page:wi.name, from: F.repoUrl('wikiedit',{name: wi.name})}), "Add attachments..." ) ) ); wi.attachments.forEach(function(a){ const wrap = D.div(); D.append(f.eAttach, wrap); D.append(wrap, D.append(D.div(), "Attachment ", D.addClass( D.a(F.repoUrl('ainfo',{name:a.uuid}), F.hashDigits(a.uuid,true)), 'monospace'), " ", a.filename, (a.isLatest ? " (latest)" : "") ) ); const ul = D.ul(); D.append(wrap, ul); [ [ "attachdownload?page=", encodeURIComponent(wi.name), "&file=", encodeURIComponent(a.filename) ].join(''), "raw/"+a.src ].forEach(function(url){ const imgUrl = D.append(D.addClass(D.span(), 'monospace'), url); const urlCopy = D.span(); const li = D.li(ul); D.append(li, urlCopy, " ", imgUrl); F.copyButton(urlCopy, {copyFromElement: imgUrl}); }); }); return this; }; P.updateEditStatus = function f(){ if(!f.eLinks){ f.eName = P.e.editStatus.querySelector('span.name'); f.eLinks = P.e.editStatus.querySelector('span.links'); } const wi = this.winfo; D.clearElement(f.eName, f.eLinks); if(!wi){ D.append(f.eName, '(no page loaded)'); this.updateAttachmentsView(); return this; } D.append(f.eName,getEditMarker(wi, false),wi.name); this.updateAttachmentsView(); if(!wi.version) return this; D.append( f.eLinks, D.a(F.repoUrl('wiki',{name:wi.name}),"viewer"), D.a(F.repoUrl('whistory',{name:wi.name}),'history'), D.a(F.repoUrl('attachlist',{page:wi.name}),"attachments"), D.a(F.repoUrl('attachadd',{page:wi.name,from: F.repoUrl('wikiedit',{name: wi.name})}), "attach"), D.a(F.repoUrl('wikiedit',{name:wi.name}),"editor permalink") ); return this; }; P.updatePageTitle = function f(){ if(!f.titleElement){ f.titleElement = document.head.querySelector('title'); } const wi = P.winfo, marker = getEditMarker(wi, true), title = wi ? wi.name : 'no page loaded'; f.titleElement.innerText = 'Wiki Editor: ' + marker + title; this.updateEditStatus(); return this; }; P.updateSaveButton = function(){ return this; }; P.wikiContent = function f(){ if(0===arguments.length){ return f.get(); }else{ f.set(arguments[0] || ''); this.dispatchEvent('wiki-content-replaced', this); return this; } }; P.wikiContent.get = function(){return P.e.taEditor.value}; P.wikiContent.set = function(content){P.e.taEditor.value = content}; P.setContentMethods = function(getter, setter){ this.wikiContent.get = getter; this.wikiContent.set = setter; return this; }; P.notifyOfChange = function(){ P._isDirty = true; P.stashContentChange(); }; P.replaceEditorElement = function(newEditor){ P.e.taEditor.parentNode.insertBefore(newEditor, P.e.taEditor); P.e.taEditor.remove(); P.e.selectFontSizeWrap.remove(); delete this.replaceEditorElement; return P; }; P.baseHrefForWiki = function f(){ this.base.tag.href = this.base.wikiUrl; return this; }; P.baseHrefRestore = function(){ this.base.tag.href = this.base.originalHref; }; P.loadPage = function(name){ if(0===arguments.length){ if(!affirmPageLoaded()) return this; name = this.winfo.name; }else if(1===arguments.length && 'string' !== typeof name){ const arg = arguments[0]; name = arg.name; } const onload = (r)=>{ this.dispatchEvent('wiki-page-loaded', r); }; const stashWinfo = this.getStashedWinfo({name: name}); if(stashWinfo){ F.message("Fetched from the local-edit storage:", stashWinfo.name); onload({ name: stashWinfo.name, mimetype: stashWinfo.mimetype, type: stashWinfo.type, version: stashWinfo.version, parent: stashWinfo.parent, isEmpty: !!stashWinfo.isEmpty, content: $stash.stashedContent(stashWinfo), attachments: stashWinfo.attachments }); this._isDirty = true; return this; } F.message( "Loading content..." ).fetch('wikiajax/fetch',{ urlParams: { page: name }, responseType: 'json', onload:(r)=>{ F.message('Loaded page ['+r.name+'].'); onload(r); } }); return this; }; P.preview = function f(switchToTab){ if(!affirmPageLoaded()) return this; return this._postPreview(this.wikiContent(), function(c){ P._previewTo(c); if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview); }); }; P._previewTo = function(c){ const target = this.e.previewTarget; D.clearElement(target); if('string'===typeof c) D.parseHtml(target,c); if(F.pikchr){ F.pikchr.addSrcView(target.querySelectorAll('svg.pikchr')); } }; P._postPreview = function(content,callback){ if(!affirmPageLoaded()) return this; if(!content){ callback(content); return this; } const fd = new FormData(); const mimetype = this.e.selectMimetype.value; fd.append('page', this.winfo.name); fd.append('mimetype',mimetype); fd.append('content',content || ''); F.message( "Fetching preview..." ).fetch('wikiajax/preview',{ payload: fd, onload: (r,header)=>{ callback(r); F.message('Updated preview.'); P.previewNeedsUpdate = false; P.dispatchEvent('wiki-preview-updated',{ mimetype: mimetype, element: P.e.previewTarget }); }, onerror: (e)=>{ F.fetch.onerror(e); callback("Error fetching preview: "+e); } }); return this; }; P.diff = function f(sbs){ if(!affirmPageLoaded()) return this; const content = this.wikiContent(), self = this, target = this.e.diffTarget; const fd = new FormData(); fd.append('page',this.winfo.name); fd.append('sbs', sbs ? 1 : 0); fd.append('content',content); if(this.e.selectDiffWS) fd.append('ws',this.e.selectDiffWS.value); F.message( "Fetching diff..." ).fetch('wikiajax/diff',{ payload: fd, onload: function(c){ D.parseHtml(D.clearElement(target), [ "
Diff [", self.winfo.name, "] → Local Edits
", c||'No changes.' ].join('')); F.diff.setupDiffContextLoad(); if(sbs) P.tweakSbsDiffs(); F.message('Updated diff.'); self.tabs.switchToTab(self.e.tabs.diff); } }); return this; }; P.save = function callee(onSuccessCallback){ if(!affirmPageLoaded()) return this; else if(!this._isDirty){ F.error("There are no changes to save."); return this; } const content = this.wikiContent(); const self = this; callee.onload = function(w){ const oldWinfo = self.winfo; self.unstashContent(oldWinfo); self.dispatchEvent('wiki-page-loaded', w); F.message("Saved page: ["+w.name+"]."); if('function'===typeof onSuccessCallback){ onSuccessCallback(); } }; const fd = new FormData(), w = P.winfo; fd.append('page',w.name); fd.append('mimetype', w.mimetype); fd.append('isnew', w.version ? 0 : 1); fd.append('content', P.wikiContent()); F.message( "Saving page..." ).fetch('wikiajax/save',{ payload: fd, responseType: 'json', onload: callee.onload }); return this; }; P.stashContentChange = function(onlyWinfo){ if(affirmPageLoaded(true)){ const wi = this.winfo; wi.mimetype = P.e.selectMimetype.value; if(onlyWinfo && $stash.hasStashedContent(wi)){ $stash.updateWinfo(wi); }else{ $stash.updateWinfo(wi, P.wikiContent()); } F.message("Stashed changes to page ["+wi.name+"]."); P.updatePageTitle(); $stash.prune(); this.previewNeedsUpdate = true; } return this; }; P.unstashContent = function(){ const winfo = arguments[0] || this.winfo; if(winfo){ this.previewNeedsUpdate = true; $stash.unstash(winfo); F.message("Unstashed page ["+winfo.name+"]."); } return this; }; P.clearStash = function(){ $stash.clear(); return this; }; P.contentFromStash = function(){ return affirmPageLoaded(true) ? $stash.stashedContent(this.winfo) : undefined; }; P.getStashedWinfo = function(winfo){ return $stash.getWinfo(winfo); }; })(window.fossil);