typora-issues icon indicating copy to clipboard operation
typora-issues copied to clipboard

[Feature] Allow editing footnotes in inline popup

Open Eric-P7 opened this issue 2 years ago • 4 comments

Hovering a footnote / citation shows it in a popup. This is quite wonderful already. But it'd be even better if I could click within that popup and edit the footnote's content.

I write long documents. This would save me a lot of time scrolling all the way to the bottom and back again.

Eric-P7 avatar Apr 22 '22 15:04 Eric-P7

relates #1571

But I think it would be ok to keep current way, since when you edit the footnote, you can already preview the formats (not in popup through). So there won't be too much scrolling..

abnerlee avatar Apr 26 '22 14:04 abnerlee

But I think it would be ok to keep current way, since when you edit the footnote, you can already preview the formats (not in popup through). So there won't be too much scrolling..

I considered doing this, but it won't work when one footnote is cited multiple times throughout the document.

Eric-P7 avatar Apr 26 '22 18:04 Eric-P7

I've written a "plugin" to allow editing footnotes inline, as I described:

  1. To install, add this code to the bottom of C:\Program Files\Typora/resources/appsrc/window/frame.js, right above the sourcMappingURL line.
  2. It adds a footnotes button to the page footer.
  3. If the footnotes popup is open, clicking a footnote in the editor will scroll to that footnote in the popup.
  4. I intercept ctrl+s to put the footnotes back where they're supposed to go before saving. Otherwise the document becomes corrupt. Saving via the menu (instead of with ctrl+s) will break the document. Wasn't sure how to intercept that way of saving.
// -----------------------------
// Footnotes Popup Plugin Start
//------------------------------

// Show a footnotes window that can be toggled from the toolbar.
// Add this code to the bottom of C:/Program Files/Typora/resources/appsrc/window/frame.js, right above the sourcMappingURL line.
let footnotesDiv;
let write = document.querySelector('#write');
let footnotesScroll = 0;

function showFootnotes() {
	if (!footnotesDiv)
		footnotesDiv = document.createElement('div');
	footnotesDiv.setAttribute('class', 'floatingFootnotes');
	footnotesDiv.setAttribute('style', 'position: sticky; bottom: 0; left: -20px; width: calc(100% + 40px); height: 20vh; z-index: 10; overflow: auto; background: inherit; padding: 10px 20px; border: 1px solid #555; border-top-left-radius: 10px; border-top-right-radius: 10px');
	write.append(footnotesDiv);
	
	
	let footnotes = document.querySelectorAll('.footnotes');
	for (let ft of footnotes)
		footnotesDiv.append(ft);
		
	footnotesDiv.scrollTop = footnotesScroll;
}

function hideFootnotes() {
	if (footnotesDiv && footnotesDiv.firstChild && footnotesDiv.firstChild.nodeType === 1 && footnotesDiv.firstChild.hasAttribute('cid')) {
		footnotesScroll = footnotesDiv.scrollTop;
		let id = parseInt(footnotesDiv.firstChild.getAttribute('cid').slice(1));
		let prevNode = document.querySelector('[cid="n'+(id-1) + '"]').nextSibling;
		for (let child of Array.from(footnotesDiv.children))
			write.insertBefore(child, prevNode);
	}
	footnotesDiv.remove();
}

setTimeout(() => {

	// Create footnotes toggle button
	let toggle = document.createElement('div');
	toggle.setAttribute('class', 'footer-item footer-item-left');
	toggle.innerHTML = '<span ty-hint="Toggle Footnotes" aria-label="Toggle Footnotes">Footnotes</span>'
	document.querySelector('footer').append(toggle);
	
	
	toggle.addEventListener('click', e => {
		if  (footnotesDiv && footnotesDiv.parentNode)
			hideFootnotes();
		else
			showFootnotes();
	});
	
	// Scroll to footnote when clicked.
	document.addEventListener('click', function(e) {
		if (!footnotesDiv || !footnotesDiv.parentNode)
			return;
	
		// Don't loos focus when editing a footnote
		if (e.target.closest('.footnotes')) 
			return;
			
		let footnote = e.target.closest('sup.md-footnote')
		if (footnote) {
			let name = footnote.dataset.ref;
			for (let ftName of document.body.querySelectorAll('.footnotes .md-def-name')) {
				if (ftName.textContent.trim() === name) {
					footnotesDiv.scrollTop = ftName.offsetTop;
				}				
			}
		}
	}, true);
		
	// Detect save via ctrl+s.  
	// Because Typora will error if we don't hide/restore the footnotes DOM to the original layout before save.
	// I don't know how to detect file->save.
	// For that case, the user should manually close the footnotes first.
	document.addEventListener('keydown', e => {
		if ((e.key === 's' || e.key === 'S') && e.ctrlKey) {
			if (footnotesDiv && footnotesDiv.parentNode) {
				hideFootnotes();
				setTimeout(showFootnotes, 100);
			}
		}	
	}, true);
	
}, 50);
// -----------------------------
// Footnotes Popup Plugin End
//------------------------------

Eric-P7 avatar Jun 20 '22 14:06 Eric-P7

I've noticed that sometimes this causes the footnotes to be eaten and lost. Not sure what's going on, but make sure you keep backups.

Eric-P7 avatar Aug 16 '22 19:08 Eric-P7