(function(F){ "use strict"; const E = (s)=>document.querySelector(s), D = F.dom, P = F.page; P.config = { defaultMaxStashSize: 7, shiftEnterPreview: F.storage.getBool('edit-shift-enter-preview', true) }; const $stash = { keys: { index: F.page.name+'.index' }, indexKey: function(finfo){return finfo.checkin+':'+finfo.filename}, 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('fileedit-stash-updated', this); }, getFinfo: function(finfo){ const ndx = this.getIndex(); return ndx[this.indexKey(finfo)]; }, storeIndex: function(){ if(this.index) F.storage.setJSON(this.keys.index,this.index); return this; }, updateFile: function(finfo,content){ const ndx = this.getIndex(), key = this.indexKey(finfo), old = ndx[key]; const record = old || (ndx[key]={ checkin: finfo.checkin, filename: finfo.filename, mimetype: finfo.mimetype }); record.isExe = !!finfo.isExe; record.stashTime = new Date().getTime(); if(!record.branch) record.branch=finfo.branch; this.storeIndex(); if(arguments.length>1){ F.storage.set(this.contentKey(key), content); } this._fireStashEvent(); return this; }, stashedContent: function(finfo){ return F.storage.get(this.contentKey(this.indexKey(finfo))); }, hasStashedContent: function(finfo){ return F.storage.contains(this.contentKey(this.indexKey(finfo))); }, unstash: function(finfo){ const ndx = this.getIndex(), key = this.indexKey(finfo); delete finfo.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; P.fileSelectWidget = { e:{ container: E('#fileedit-file-selector') }, finfo: {}, cache: { checkins: undefined, files:{}, branchKey: 'fileedit/uuid-branches', branchNames: {} }, loadLeaves: function(){ D.append(D.clearElement( this.e.ciListLabel, this.e.selectCi, this.e.selectFiles ),"Loading leaves..."); D.disable(this.e.btnLoadFile, this.e.selectFiles, this.e.selectCi); const self = this; const onload = function(list){ D.append(D.clearElement(self.e.ciListLabel), "Open leaves (newest first):"); self.cache.checkins = list; D.clearElement(D.enable(self.e.selectCi)); let loadThisOne = P.initialFiles; if(loadThisOne){ self.cache.files[loadThisOne.checkin] = loadThisOne; delete P.initialFiles; } list.forEach(function(o,n){ if(!n && !loadThisOne) loadThisOne = o; self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch; D.option(self.e.selectCi, o.checkin, o.timestamp+' ['+o.branch+']: ' +F.hashDigits(o.checkin)); }); F.storage.setJSON(self.cache.branchKey, self.cache.branchNames); if(loadThisOne){ self.e.selectCi.value = loadThisOne.checkin; } self.loadFiles(loadThisOne ? loadThisOne.checkin : false); }; if(P.initialLeaves){ const lv = P.initialLeaves; delete P.initialLeaves; onload(lv); }else{ F.fetch('fileedit/filelist',{ urlParams:'leaves', responseType: 'json', onload: onload }); } }, loadFiles: function(ciUuid){ delete this.finfo.filename; this.finfo.checkin = ciUuid; const selFiles = this.e.selectFiles; if(!ciUuid){ D.clearElement(D.disable(selFiles, this.e.btnLoadFile)); return this; } const onload = (response)=>{ D.clearElement(selFiles); D.append( D.clearElement(this.e.fileListLabel), "Editable files for ", D.append( D.code(), "[", D.a(F.repoUrl('timeline',{ c: ciUuid }), F.hashDigits(ciUuid)),"]" ), ":" ); this.cache.files[response.checkin] = response; response.editableFiles.forEach(function(fn,n){ D.option(selFiles, fn); }); if(selFiles.options.length){ D.enable(selFiles, this.e.btnLoadFile); } }; const got = this.cache.files[ciUuid]; if(got){ onload(got); return this; } D.disable(selFiles,this.e.btnLoadFile); D.clearElement(selFiles); D.append(D.clearElement(this.e.fileListLabel), "Loading files for "+F.hashDigits(ciUuid)+"..."); F.fetch('fileedit/filelist',{ urlParams:{checkin: ciUuid}, responseType: 'json', onload }); return this; }, checkinBranchName: function(uuid){ return this.cache.branchNames[F.hashDigits(uuid,true)]; }, init: function(){ this.cache.branchNames = F.storage.getJSON(this.cache.branchKey, {}); const selCi = this.e.selectCi = D.addClass(D.select(), 'flex-grow'), selFiles = this.e.selectFiles = D.addClass(D.select(), 'file-list'), btnLoad = this.e.btnLoadFile = D.addClass(D.button("Load file"), "flex-shrink"), filesLabel = this.e.fileListLabel = D.addClass(D.div(),'flex-shrink','file-list-label'), ciLabelWrapper = D.addClass( D.div(), 'flex-container','flex-row', 'flex-shrink', 'stretch', 'child-gap-small' ), btnReload = D.addClass( D.button('Reload'), 'flex-shrink' ), ciLabel = this.e.ciListLabel = D.addClass(D.span(),'flex-shrink','checkin-list-label') ; D.attr(selCi, 'title',"The list of opened leaves."); D.attr(selFiles, 'title', "The list of editable files for the selected checkin."); D.attr(btnLoad, 'title', "Load the selected file into the editor."); D.disable(selCi, selFiles, btnLoad); D.attr(selFiles, 'size', 12); D.append( this.e.container, ciLabel, D.append(ciLabelWrapper, selCi, btnReload), filesLabel, selFiles, D.append(D.addClass(D.div(), 'flex-shrink'), btnLoad) ); if(F.config['fileedit-glob']){ D.append( this.e.container, D.append( D.span(), D.append(D.code(),"fileedit-glob"), " config setting = ", D.append(D.code(), JSON.stringify(F.config['fileedit-glob'])) ) ); } this.loadLeaves(); selCi.addEventListener( 'change', (e)=>this.loadFiles(e.target.value), false ); const doLoad = (e)=>{ this.finfo.filename = selFiles.value; if(this.finfo.filename){ P.loadFile(this.finfo.filename, this.finfo.checkin); } }; btnLoad.addEventListener('click', doLoad, false); selFiles.addEventListener('dblclick', doLoad, false); btnReload.addEventListener( 'click', (e)=>this.loadLeaves(), false ); delete this.init; } }; P.stashWidget = { e:{}, init: function(domInsertPoint){ const wrapper = D.addClass( D.attr(D.div(),'id','fileedit-stash-selector'), 'input-with-label' ); const sel = this.e.select = D.select(); const btnClear = this.e.btnClear = D.button("Discard Edits"), btnHelp = D.append( D.addClass(D.div(), "help-buttonlet"), 'Locally-edited files. Timestamps are the last local edit time. ', 'Only the ',P.config.defaultMaxStashSize,' most recent files ', '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)"); F.page.addEventListener('fileedit-stash-updated',(e)=>this.updateList(e.detail)); F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail)); sel.addEventListener('change',function(e){ const opt = this.selectedOptions[0]; if(opt && opt._finfo) P.loadFile(opt._finfo); }); 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); P.tabs.switchToTab(1); F.confirmer(btnClear, { pinSize: true, confirmText: "DISCARD all local edits?", onconfirm: function(e){ if(P.finfo){ const stashed = P.getStashedFinfo(P.finfo); P.clearStash(); if(stashed) P.loadFile(); }else{ P.clearStash(); } }, ticks: F.config.confirmerButtonTicks }); D.addClass(this.e.btnClear,'hidden'); $stash._fireStashEvent(); P.tabs.switchToTab(0); delete this.init; }, updateList: function f(stasher,theFinfo){ if(!f.compare){ const cmpBase = (l,r)=>l(''+x).length>1 ? x : '0'+x; f.timestring = function ff(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((finfo)=>{ ilist.push(index[finfo]); }); 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); D.removeClass(this.e.btnClear, 'hidden'); D.disable(D.option(this.e.select,0,"Select a local edit...")); const currentFinfo = theFinfo || P.finfo || {filename:''}; ilist.sort(f.compare).forEach(function(finfo,n){ const key = stasher.indexKey(finfo), branch = finfo.branch || P.fileSelectWidget.checkinBranchName(finfo.checkin)||''; const opt = D.option( self.e.select, n+1, [F.hashDigits(finfo.checkin), ' [',branch||'?branch?','] ', f.timestring(new Date(finfo.stashTime)),' ', false ? finfo.filename : F.shortenFilename(finfo.filename) ].join('') ); opt._finfo = finfo; if(0===f.compare(currentFinfo, finfo)){ D.attr(opt, 'selected', true); } }); } }; P.selectPreviewMode = function(modeValue, forceEvent){ const s = this.e.selectPreviewMode; 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 ajaxState = { count: 0, toDisable: undefined }; F.fetch.beforesend = function f(){ if(!ajaxState.toDisable){ ajaxState.toDisable = document.querySelectorAll( 'button, input, select, textarea' ); } 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); } }; F.onPageLoad(function() { P.base = {tag: E('base')}; P.base.originalHref = P.base.tag.href; P.tabs = new F.TabManager('#fileedit-tabs'); P.e = { taEditor: E('#fileedit-content-editor'), taCommentSmall: E('#fileedit-comment'), taCommentBig: E('#fileedit-comment-big'), taComment: undefined, ajaxContentTarget: E('#ajax-target'), btnCommit: E("#fileedit-btn-commit"), btnReload: E("#fileedit-tab-content button.fileedit-content-reload"), selectPreviewMode: E('#select-preview-mode select'), selectHtmlEmsWrap: E('#select-preview-html-ems'), selectEolWrap: E('#select-eol-style'), selectEol: E('#select-eol-style select[name=eol]'), selectFontSizeWrap: E('#select-font-size'), selectDiffWS: E('select[name=diff_ws]'), cbLineNumbersWrap: E('#cb-line-numbers'), cbAutoPreview: E('#cb-preview-autorefresh'), previewTarget: E('#fileedit-tab-preview-wrapper'), manifestTarget: E('#fileedit-manifest'), diffTarget: E('#fileedit-tab-diff-wrapper'), cbIsExe: E('input[type=checkbox][name=exec_bit]'), cbManifest: E('input[type=checkbox][name=include_manifest]'), editStatus: E('#fileedit-edit-status'), tabs:{ content: E('#fileedit-tab-content'), preview: E('#fileedit-tab-preview'), diff: E('#fileedit-tab-diff'), commit: E('#fileedit-tab-commit'), fileSelect: E('#fileedit-tab-fileselect') } }; if(D.hasClass(P.e.taCommentSmall, 'hidden')){ P.e.taComment = P.e.taCommentBig; }else if(D.hasClass(P.e.taCommentBig,'hidden')){ P.e.taComment = P.e.taCommentSmall; }else{ P.e.taComment = P.e.taCommentSmall; D.addClass(P.e.taCommentBig, 'hidden'); } D.removeClass(P.e.taComment, 'hidden'); P.tabs.addCustomWidget( E('#fossil-status-bar') ).addCustomWidget(P.e.editStatus); let currentTab; P.tabs.addEventListener( 'before-switch-to', function(ev){ currentTab = ev.detail; if(ev.detail===P.e.tabs.preview){ P.baseHrefForFile(); if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview(); }else if(ev.detail===P.e.tabs.diff){ D.removeClass(P.e.diffTarget, 'hidden'); } } ); P.tabs.addEventListener( 'before-switch-from', function(ev){ if(ev.detail===P.e.tabs.preview){ P.baseHrefRestore(); }else if(ev.detail===P.e.tabs.diff){ D.addClass(P.e.diffTarget, 'hidden'); } } ); P.e.taEditor.addEventListener('keydown',function(ev){ if(P.config.shiftEnterPreview && 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(); } return false; } }, 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('#fileedit-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 ); P.e.btnCommit.addEventListener( "click",(e)=>P.commit(), false ); P.tabs.switchToTab(1); F.confirmer(P.e.btnReload, { pinSize: true, confirmText: "Really reload, losing edits?", onconfirm: (e)=>P.unstashContent().loadFile(), ticks: F.config.confirmerButtonTicks }); E('#comment-toggle').addEventListener( "click",(e)=>P.toggleCommentMode(), false ); P.e.taEditor.addEventListener('change', ()=>P.notifyOfChange(), false); P.e.cbIsExe.addEventListener( 'change', ()=>P.stashContentChange(true), false ); P.e.selectPreviewMode.addEventListener( "change", function(e){ const mode = e.target.value, name = P.previewModes[mode], hide = [], unhide = []; P.previewModes.current = name; if('guess'===name){ unhide.push(P.e.cbLineNumbersWrap, P.e.selectHtmlEmsWrap); }else{ if('text'===name) unhide.push(P.e.cbLineNumbersWrap); else hide.push(P.e.cbLineNumbersWrap); if('htmlIframe'===name) unhide.push(P.e.selectHtmlEmsWrap); else hide.push(P.e.selectHtmlEmsWrap); } hide.forEach((e)=>e.classList.add('hidden')); unhide.forEach((e)=>e.classList.remove('hidden')); }, false ); P.selectPreviewMode(false, true); 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( 'fileedit-content-replaced', ()=>{ P.previewNeedsUpdate = true; D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget); } ); P.addEventListener( 'fileedit-committed', (e)=>{ if(!e.detail.dryRun){ D.clearElement(P.e.diffTarget, P.e.previewTarget); } } ); P.fileSelectWidget.init(); P.stashWidget.init( P.e.tabs.content.lastElementChild ); const cbEditPreview = E('#edit-shift-enter-preview'); cbEditPreview.addEventListener('change', function(e){ F.storage.set('edit-shift-enter-preview', P.config.shiftEnterPreview = e.target.checked); }, false); cbEditPreview.checked = P.config.shiftEnterPreview; }); P.fileContent = function f(){ if(0===arguments.length){ return f.get(); }else{ f.set(arguments[0] || ''); this.dispatchEvent('fileedit-content-replaced', this); return this; } }; P.fileContent.get = function(){return P.e.taEditor.value}; P.fileContent.set = function(content){P.e.taEditor.value = content}; P.setContentMethods = function(getter, setter){ this.fileContent.get = getter; this.fileContent.set = setter; return this; }; P.notifyOfChange = function(){ 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.baseHrefForFile = function f(){ const fn = this.finfo ? this.finfo.filename : undefined; if(!fn) return this; if(!f.wikiMimeTypes){ f.wikiMimeTypes = ["text/x-fossil-wiki", "text/x-markdown"]; } if('wiki'===P.previewModes.current || ('guess'===P.previewModes.current && f.wikiMimeTypes.indexOf(this.finfo.mimetype)>=0)){ const a = fn.split('/'); a.pop(); this.base.tag.href = F.repoUrl( 'doc/'+F.hashDigits(this.finfo.checkin) +'/'+(a.length ? a.join('/')+'/' : '') ); } return this; }; P.baseHrefRestore = function(){ P.base.tag.href = P.base.originalHref; }; P.toggleCommentMode = function(){ var s, h, c = this.e.taComment.value; if(this.e.taComment === this.e.taCommentSmall){ s = this.e.taCommentBig; h = this.e.taCommentSmall; }else{ s = this.e.taCommentSmall; h = this.e.taCommentBig; c = c.replace(/\r?\n/g,' '); } s.value = c; this.e.taComment = s; D.addClass(h, 'hidden'); D.removeClass(s, 'hidden'); }; const affirmHasFile = function(quiet){ if(!P.finfo){ if(!quiet) F.error("No file is loaded."); } return !!P.finfo; }; P.updateVersion = function f(file,rev){ if(!f.eLinks){ f.eName = P.e.editStatus.querySelector('span.name'); f.eLinks = P.e.editStatus.querySelector('span.links'); } if(1===arguments.length){ this.finfo = arguments[0]; file = this.finfo.filename; rev = this.finfo.checkin; }else if(0===arguments.length){ if(affirmHasFile()){ file = this.finfo.filename; rev = this.finfo.checkin; } }else{ this.finfo = {filename:file,checkin:rev}; } const fi = this.finfo; D.clearElement(f.eName, f.eLinks); if(!fi){ D.append(f.eName, '(no file loaded)'); return this; } const rHuman = F.hashDigits(rev), rUrl = F.hashDigits(rev,true); D.append(f.eName,D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file)); D.append( f.eLinks, D.append(D.span(), fi.mimetype||'?mimetype?'), D.a(F.repoUrl('info/'+rUrl), rHuman), D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"), D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),'annotate'), D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),'blame') ); const purlArgs = F.encodeUrlArgs({ filename: this.finfo.filename, checkin: rUrl },false,true); const purl = F.repoUrl('fileedit',purlArgs); D.append( f.eLinks, D.a(purl,"editor permalink") ); this.setPageTitle("Edit: "+fi.filename); return this; }; P.loadFile = function(file,rev){ if(0===arguments.length){ if(!affirmHasFile()) return this; file = this.finfo.filename; rev = this.finfo.checkin; }else if(1===arguments.length){ const arg = arguments[0]; file = arg.filename; rev = arg.checkin; } const self = this; const onload = (r,headers)=>{ delete self.finfo; self.updateVersion({ filename: file, checkin: rev, branch: headers['x-fileedit-checkin-branch'], isExe: ('x'===headers['x-fileedit-file-perm']), mimetype: headers['content-type'].split(';').shift() }); self.tabs.switchToTab(self.e.tabs.content); self.e.cbIsExe.checked = self.finfo.isExe; self.fileContent(r); P.previewNeedsUpdate = true; self.dispatchEvent('fileedit-file-loaded', self.finfo); }; const semiFinfo = {filename: file, checkin: rev}; const stashFinfo = this.getStashedFinfo(semiFinfo); if(stashFinfo){ this.finfo = stashFinfo; this.e.cbIsExe.checked = !!stashFinfo.isExe; onload(this.contentFromStash()||'',{ 'x-fileedit-file-perm': stashFinfo.isExe ? 'x' : undefined, 'content-type': stashFinfo.mimetype, 'x-fileedit-checkin-branch': stashFinfo.branch }); F.message("Fetched from the local-edit storage:", F.hashDigits(stashFinfo.checkin), stashFinfo.filename); return this; } F.message( "Loading content..." ).fetch('fileedit/content',{ urlParams: { filename:file, checkin:rev }, responseHeaders: [ 'x-fileedit-file-perm', 'x-fileedit-checkin-branch', 'content-type'], onload:(r,headers)=>{ onload(r,headers); F.message('Loaded content for', F.hashDigits(self.finfo.checkin), self.finfo.filename); } }); return this; }; P.preview = function f(switchToTab){ if(!affirmHasFile()) return this; return this._postPreview(this.fileContent(), 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(!affirmHasFile()) return this; if(!content){ callback(content); return this; } const fd = new FormData(); fd.append('render_mode',this.e.selectPreviewMode.value); fd.append('filename',this.finfo.filename); fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0); fd.append('iframe_height', E('[name=preview_html_ems]').value); fd.append('content',content || ''); F.message( "Fetching preview..." ).fetch('ajax/preview-text',{ payload: fd, responseHeaders: 'x-ajax-render-mode', onload: (r,header)=>{ P.selectPreviewMode(P.previewModes[header]); if('wiki'===header) P.baseHrefForFile(); else P.baseHrefRestore(); callback(r); F.message('Updated preview.'); P.previewNeedsUpdate = false; P.dispatchEvent('fileedit-preview-updated',{ previewMode: P.previewModes.current, mimetype: P.finfo.mimetype, element: P.e.previewTarget }); }, onerror: (e)=>{ F.fetch.onerror(e); callback("Error fetching preview: "+e); } }); return this; }; P.diff = function f(sbs){ if(!affirmHasFile()) return this; const content = this.fileContent(), self = this, target = this.e.diffTarget; const fd = new FormData(); fd.append('filename',this.finfo.filename); fd.append('checkin', this.finfo.checkin); 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('fileedit/diff',{ payload: fd, onload: function(c){ D.parseHtml(D.clearElement(target),[ "
Diff [", self.finfo.checkin, "] → 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.commit = function f(){ if(!affirmHasFile()) return this; const self = this; const content = this.fileContent(), target = D.clearElement(P.e.manifestTarget), cbDryRun = E('[name=dry_run]'), isDryRun = cbDryRun.checked, filename = this.finfo.filename; if(!f.onload){ f.onload = function(c){ const oldFinfo = JSON.parse(JSON.stringify(self.finfo)) if(c.manifest){ D.parseHtml(D.clearElement(target), [ "

Manifest", (c.dryRun?" (dry run)":""), ": ", F.hashDigits(c.checkin),"

", "
",
c.manifest.replace(/
" ].join('')); delete c.manifest; } const msg = [ 'Committed', c.dryRun ? '(dry run)' : '', '[', F.hashDigits(c.checkin) ,'].' ]; if(!c.dryRun){ self.unstashContent(oldFinfo); self.finfo = c; self.e.taComment.value = ''; self.updateVersion(); self.fileSelectWidget.loadLeaves(); } self.dispatchEvent('fileedit-committed', c); F.message.apply(F, msg); self.tabs.switchToTab(self.e.tabs.commit); }; } const fd = new FormData(); fd.append('filename',filename); fd.append('checkin', this.finfo.checkin); fd.append('content',content); fd.append('dry_run',isDryRun ? 1 : 0); fd.append('eol', this.e.selectEol.value || 0); fd.append('comment', this.e.taComment.value); if(0){ ['comment_mimetype' ].forEach(function(name){ var e = E('[name='+name+']'); if(e) fd.append(name,e.value); }); } ['allow_fork', 'allow_older', 'exec_bit', 'allow_merge_conflict', 'include_manifest', 'prefer_delta' ].forEach(function(name){ var e = E('[name='+name+']'); if(e){ fd.append(name, e.checked ? 1 : 0); }else{ console.error("Missing checkbox? name =",name); } }); F.message( "Checking in..." ).fetch('fileedit/commit',{ payload: fd, responseType: 'json', onload: f.onload }); return this; }; P.stashContentChange = function(onlyFinfo){ if(affirmHasFile(true)){ const fi = this.finfo; fi.isExe = this.e.cbIsExe.checked; if(!fi.branch) fi.branch = this.fileSelectWidget.checkinBranchName(fi.checkin); if(onlyFinfo && $stash.hasStashedContent(fi)){ $stash.updateFile(fi); }else{ $stash.updateFile(fi, P.fileContent()); } F.message("Stashed change to",F.hashDigits(fi.checkin),fi.filename); $stash.prune(); this.previewNeedsUpdate = true; } return this; }; P.unstashContent = function(){ const finfo = arguments[0] || this.finfo; if(finfo){ this.previewNeedsUpdate = true; $stash.unstash(finfo); F.message("Unstashed",F.hashDigits(finfo.checkin),finfo.filename); } return this; }; P.clearStash = function(){ $stash.clear(); return this; }; P.contentFromStash = function(){ return affirmHasFile(true) ? $stash.stashedContent(this.finfo) : undefined; }; P.getStashedFinfo = function(finfo){ return $stash.getFinfo(finfo); }; P.$stash = $stash; })(window.fossil);