Saves a copy of the editor's content to local storage at set intervals so you can recover it if you forget to save or lose connection.
// ==UserScript==
// @name Kanka Autosaves
// @namespace http://tampermonkey.net/
// @version 1
// @description Saves a copy of the editor's content to local storage at set intervals so you can recover it if you forget to save or lose connection.
// @author Salvatos
// @license MIT
// @match https://app.kanka.io/*
// @icon https://www.google.com/s2/favicons?domain=kanka.io
// @run-at document-end
// ==/UserScript==
/* Preferences */
const interval = 5; // in minutes
const snapshots = 10; // how many snapshots to keep in storage
// Wait for Summernote to initialize
$('#entry').on('summernote.init', function() {
// Collect some info about the entry
const crumb = document.querySelector('.breadcrumb li:nth-last-child(2) a');
const nameField = document.querySelector('.entity-form input[name="name"]').value;
const entityName = (crumb.href.match(/entities/)) ? crumb.title : (nameField.length > 0) ? nameField + " <i>(unsaved entity)</i>)" : "<i>(Unnamed " + crumb.title + " entity)</i>";
const entryType = window.location.href.match(/posts/) ? "post" : "entry";
const postName = (entryType == "post") ? (nameField.length > 0) ? nameField : "<i>(New post)</i>" : null;
var recoveryButton = `
<button type="button" id="autosavesButton" class="note-btn btn btn-default btn-sm" tabindex="-1" title="View autosaves in local storage" aria-label="View autosaves in local storage" data-original-title="View autosaves in local storage">
<i class="fa-solid fa-clock-rotate-left"></i>
</button>
`;
document.getElementsByClassName('note-toolbar')[0].insertAdjacentHTML("beforeend", recoveryButton);
document.body.insertAdjacentHTML("beforeend", `<dialog id="autosaves" class="bg-box rounded border text-base-content"><header class="text-right"><button class="px-3 py-2" autofocus>Close</button></header><output style="max-height: 80vh;overflow-y: auto;padding-inline: 1.25rem;"></output></dialog>`);
// Grab our button and dialog instances
recoveryButton = document.getElementById('autosavesButton');
const dialog = document.querySelector("#autosaves");
const closeButton = document.querySelector("#autosaves button");
// Add click event to buttons
closeButton.addEventListener("click", () => {
dialog.close();
dialog.querySelector('output').innerHTML = "";
});
recoveryButton.addEventListener('click', ()=>{
dialog.showModal();
let savedState = JSON.parse( localStorage.getItem('kanka-autosaves') );
// If we have existing snapshots, show them
if (savedState && savedState.length > 0) {
savedState.forEach( (state) => {
// Create a Date object from the timestamp
const date = new Date(state.time);
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short',
hour12: false
};
const formattedTime = date.toLocaleString('en-US', options);
dialog.querySelector('output').insertAdjacentHTML("beforeend", `<h4 class="text-primary my-1">${state.title}</h4><p class="text-accent-content text-xs">${formattedTime}</p><div>${state.content.substring(0,50)}... <p class="text-accent cursor-pointer">→ Paste into editor</p></div><hr class="my-2">`);
});
// Copy to Summernote
dialog.querySelectorAll('output div').forEach(function(elem, i) {
elem.addEventListener("click", ()=> {
try {
$('#entry').summernote('pasteHTML', savedState[i].content);
} catch {
alert("Failed to paste into the editor");
}
});
});
}
else {
alert("No snapshot found in local storage.");
}
});
// Autosave
autosave();
function autosave () {
setTimeout(() => {
var editorValue = "";
// Code editor
if ($('#entry + div').hasClass('codeview')) {
editorValue = $('#entry + div').find('.note-codable').val();
}
// Visual editor
else {
editorValue = $('#entry').val();
}
let formattedName = (entryType == "post") ? postName + " (" + entityName + ")" : entityName;
let jsonItem = {time: Date.now(), title: formattedName, type: entryType, content: editorValue };
let savedState = JSON.parse( localStorage.getItem('kanka-autosaves') );
if (savedState) {
// Remove excess saves
while (savedState.length > 0 && savedState.length >= snapshots) {
savedState.splice(0, 1);
}
// Add to array and save
savedState.push(jsonItem);
localStorage.setItem(('kanka-autosaves'), JSON.stringify(savedState));
}
else {
// First save, create array
localStorage.setItem(('kanka-autosaves'), JSON.stringify([jsonItem]));
}
// Re-run
autosave();
}, (1000 * 60 * interval));
}
});