storage.js (3.4 KB)


  1 // Storage: localStorage persistence, file import/export, serialization
  2 
  3 function scheduleSave() {
  4 	if (state.saveTimer) {
  5 		clearTimeout(state.saveTimer);
  6 	}
  7 	state.saveTimer = setTimeout(saveTasksToLocalStorage, 1000);
  8 }
  9 
 10 function saveTasksToLocalStorage() {
 11 	var serializedTasks = serializeTaskTree(state.taskPath[0]);
 12 	localStorage.setItem('taskTree', serializedTasks);
 13 	state.saveTimer = null;
 14 }
 15 
 16 function serializeTaskTree(task, depth = 0) {
 17 	var indentation = '\t'.repeat(depth);
 18 	var status = task.state === 0 ? '_' : (task.state === 1 ? 'x' : '?');
 19 	var serialized = `${indentation}${status} ${task.text}\n`;
 20 
 21 	for (var subtask of task.subtasks) {
 22 		serialized += serializeTaskTree(subtask, depth + 1);
 23 	}
 24 
 25 	return serialized;
 26 }
 27 
 28 function deserializeTaskTree(serialized) {
 29 	var lines = serialized.split('\n').filter(line => line.trim() !== '');
 30 	var root = { id: 'root', subtasks: [] };
 31 	var stack = [{ task: root, depth: -1 }];
 32 
 33 	for (var line of lines) {
 34 		var depth = (line.match(/^\t*/)[0] || '').length;
 35 		var status = line[depth];
 36 		var text = line.slice(depth + 2);
 37 
 38 		var newTask = {
 39 			id: depth === 0 ? 'root' : generateId(),
 40 			text: text,
 41 			state: status === '_' ? 0 : (status === 'x' ? 1 : 2),
 42 			subtasks: [],
 43 			selectedSubtaskId: null
 44 		};
 45 
 46 		while (stack.length > 1 && stack[stack.length - 1].depth >= depth) {
 47 			stack.pop();
 48 		}
 49 
 50 		if (depth === 0) {
 51 			Object.assign(root, newTask);
 52 		} else {
 53 			stack[stack.length - 1].task.subtasks.push(newTask);
 54 		}
 55 		stack.push({ task: newTask, depth: depth });
 56 	}
 57 
 58 	return root;
 59 }
 60 
 61 function loadTasksFromLocalStorage() {
 62 	var savedTasks = localStorage.getItem('taskTree');
 63 	if (savedTasks) {
 64 		console.log('%cloaded tasks from local storage:', "color: green;");
 65 		console.log(savedTasks);
 66 		return deserializeTaskTree(savedTasks);
 67 	} else {
 68 		return { id: 'root', text: 'todo', state: 0, subtasks: [{ id: generateId(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };
 69 	}
 70 }
 71 
 72 function handleSave(e) {
 73 	if ((e.metaKey || e.ctrlKey) && e.key === 's') {
 74 		e.preventDefault();
 75 		saveTaskTreeToFile();
 76 	}
 77 }
 78 
 79 function saveTaskTreeToFile() {
 80 	var serializedTasks = serializeTaskTree(state.taskPath[0]);
 81 	var rootTaskName = state.taskPath[0].text || 'Untitled';
 82 	var date = new Date();
 83 	var fileName = `${rootTaskName} - ${date.toLocaleString('default', { month: 'short' }).toLowerCase()} ${date.getDate()}, '${date.getFullYear().toString().slice(-2)}.txt`;
 84 
 85 	var blob = new Blob([serializedTasks], { type: 'text/plain' });
 86 	var url = URL.createObjectURL(blob);
 87 
 88 	var a = document.createElement('a');
 89 	a.href = url;
 90 	a.download = fileName;
 91 	a.click();
 92 
 93 	URL.revokeObjectURL(url);
 94 }
 95 
 96 function handleOpen(e) {
 97 	if ((e.metaKey || e.ctrlKey) && e.key === 'o') {
 98 		e.preventDefault();
 99 		openTaskTreeFromFile();
100 	}
101 }
102 
103 function openTaskTreeFromFile() {
104 	var input = document.createElement('input');
105 	input.type = 'file';
106 	input.accept = '.txt';
107 	input.onchange = function(event) {
108 		var file = event.target.files[0];
109 		var reader = new FileReader();
110 		reader.onload = function(e) {
111 			try {
112 				var newRootTask = deserializeTaskTree(e.target.result);
113 				if (confirm('Are you sure you want to overwrite the existing task tree?')) {
114 					state.rootTask = newRootTask;
115 					state.currentTask = state.rootTask;
116 					state.taskPath = [state.currentTask];
117 					renderCurrentView();
118 					saveTasksToLocalStorage();
119 				}
120 			} catch (error) {
121 				alert(`Error importing task tree: ${error.message}`);
122 			}
123 		};
124 		reader.readAsText(file);
125 	};
126 	input.click();
127 }