1 import { Outline, RawOutline } from './outline';
2 import { Cursor } from './cursor';
3 import keyboardJS from 'keyboardjs';
4 import * as rawOutline from './test-data.json';
5 import {showHelp} from 'help';
6 import { Search } from './search';
8 let outlineData = rawOutline;
9 if(localStorage.getItem('activeOutline')) {
10 const outlineId = localStorage.getItem('activeOutline');
11 outlineData = JSON.parse(localStorage.getItem(outlineId));
14 const state = new Map<string, any>();
15 const outline = new Outline(outlineData as unknown as RawOutline);
16 outliner().innerHTML = outline.render();
18 const cursor = new Cursor();
19 // place the cursor at the top!
22 const search = new Search();
25 return document.querySelector('#outliner');
28 document.getElementById('display-help').addEventListener('click', e => {
36 keyboardJS.withContext('navigation', () => {
37 keyboardJS.bind('j', e => {
39 // if shift key is held, swap the node with its next sibling
40 const sibling = cursor.get().nextElementSibling;
44 // swap this node with its previous sibling
45 const res = outline.swapNodeWithNextSibling(cursor.getIdOfNode());
46 const html = outline.renderNode(res.parentNode);
48 if(res.parentNode.id === '000000') {
49 cursor.get().parentElement.innerHTML = html;
52 cursor.get().parentElement.outerHTML = html;
55 cursor.set(`#id-${res.targetNode.id}`);
59 cursor.set(`#id-${sibling.getAttribute('data-id')}`);
65 keyboardJS.bind('shift + /', e => {
69 keyboardJS.bind('k', e => {
71 // if shift key is held, swap the node with its previous sibling
72 const sibling = cursor.get().previousElementSibling;
74 if(sibling && !sibling.classList.contains('nodeContent')) {
76 // swap this node with its previous sibling
77 const res = outline.swapNodeWithPreviousSibling(cursor.getIdOfNode());
78 // re-render the parent node and display that!
79 const html = outline.renderNode(res.parentNode);
81 if(res.parentNode.id === '000000') {
82 cursor.get().parentElement.innerHTML = html;
85 cursor.get().parentElement.outerHTML = html;
88 cursor.set(`#id-${res.targetNode.id}`);
92 cursor.set(`#id-${sibling.getAttribute('data-id')}`);
97 keyboardJS.bind('l', e => {
98 // if the node is collapsed, we can't go into its children
99 if(cursor.isNodeCollapsed()) {
103 const res = outline.lowerNodeToChild(cursor.getIdOfNode());
104 const html = outline.renderNode(res.oldParentNode);
106 if(res.oldParentNode.id === '000000') {
107 cursor.get().parentElement.innerHTML = html;
110 cursor.get().parentElement.outerHTML = html;
112 cursor.set(`#id-${res.targetNode.id}`);
115 const children = cursor.get().querySelector('.node');
117 cursor.set(`#id-${children.getAttribute('data-id')}`);
122 keyboardJS.bind('h', e => {
123 const parent = cursor.get().parentElement;
124 if(parent && parent.classList.contains('node')) {
126 if(outline.data.tree.children.map(n => n.id).includes(cursor.getIdOfNode())) {
127 // if this is a top level item, we can't elevate any further
130 const res = outline.liftNodeToParent(cursor.getIdOfNode());
132 const html = outline.renderNode(res.parentNode);
134 if(res.parentNode.id === '000000') {
135 cursor.get().parentElement.parentElement.innerHTML = html;
138 cursor.get().parentElement.parentElement.outerHTML = html;
141 cursor.set(`#id-${res.targetNode.id}`);
145 cursor.set(`#id-${parent.getAttribute('data-id')}`);
150 keyboardJS.bind('z', e => {
152 if(cursor.isNodeExpanded()) {
154 outline.fold(cursor.getIdOfNode());
156 else if(cursor.isNodeCollapsed()) {
158 outline.unfold(cursor.getIdOfNode());
163 keyboardJS.bind('shift + 4', e => {
165 // switch to editing mode
166 cursor.get().classList.add('hidden-cursor');
167 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
169 // swap the content to the default!
170 contentNode.innerHTML = outline.data.contentNodes[cursor.getIdOfNode()].content;
171 contentNode.contentEditable = "true";
173 const range = document.createRange();
174 range.selectNodeContents(contentNode);
175 range.collapse(false);
177 const selection = window.getSelection();
178 selection.removeAllRanges();
179 selection.addRange(range);
182 keyboardJS.setContext('editing');
185 keyboardJS.bind('i', e => {
187 // switch to editing mode
188 cursor.get().classList.add('hidden-cursor');
189 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
191 // swap the content to the default!
192 contentNode.innerHTML = outline.data.contentNodes[cursor.getIdOfNode()].content;
193 contentNode.contentEditable = "true";
195 keyboardJS.setContext('editing');
198 keyboardJS.bind('shift + x', e => {
200 // toggle "strikethrough" of node
201 cursor.get().classList.toggle('strikethrough');
202 outline.data.contentNodes[cursor.getIdOfNode()].strikethrough = cursor.get().classList.contains('strikethrough');
206 keyboardJS.bind('tab', e => {
209 const res = outline.createChildNode(cursor.getIdOfNode());
210 const html = outline.renderNode(res.parentNode);
212 cursor.get().outerHTML = html;
214 cursor.set(`#id-${res.node.id}`);
218 keyboardJS.bind('enter', e => {
219 // create a new node as a sibling of the selected node
226 const res = outline.createSiblingNode(cursor.getIdOfNode());
228 const html = outline.renderNode(res.parentNode);
229 if(res.parentNode.id === '000000') {
230 cursor.get().parentElement.innerHTML = html;
233 cursor.get().parentElement.outerHTML = html;
236 cursor.set(`#id-${res.node.id}`);
240 keyboardJS.bind('d', e => {
241 // deleting a node requires d + shift
246 const res = outline.removeNode(cursor.getIdOfNode());
247 const html = outline.renderNode(res.parentNode);
248 // the previous sibling!
249 const prevSibling = cursor.get().previousElementSibling;
250 const nextSibling = cursor.get().nextElementSibling;
251 if(res.parentNode.id === '000000') {
252 cursor.get().parentElement.innerHTML = html;
255 cursor.get().parentElement.outerHTML = html;
258 if(prevSibling.getAttribute('data-id')) {
259 cursor.set(`#id-${prevSibling.getAttribute('data-id')}`);
261 else if(nextSibling.getAttribute('data-id')) {
262 cursor.set(`#id-${nextSibling.getAttribute('data-id')}`);
265 console.log(res.parentNode.id);
266 cursor.set(`#id-${res.parentNode.id}`);
273 keyboardJS.withContext('editing', () => {
274 keyboardJS.bind(['esc', 'enter'], e => {
275 cursor.get().classList.remove('hidden-cursor');
277 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
279 contentNode.contentEditable = "false";
281 keyboardJS.setContext('navigation');
283 outline.updateContent(cursor.getIdOfNode(), contentNode.innerHTML.trim());
284 // re-render this node!
285 contentNode.innerHTML = outline.renderContent(cursor.getIdOfNode());
290 keyboardJS.setContext('navigation');
297 strikethrough: "boolean"
298 }).then(async () => {
299 await search.indexBatch(outline.data.contentNodes);
303 function recursivelyExpand(start: HTMLElement) {
304 if(start.classList.contains('node')) {
305 if(start.classList.contains('collapsed')) {
306 start.classList.remove('collapsed');
307 start.classList.add('expanded');
308 outline.unfold(start.getAttribute('data-id'));
311 if(start.parentElement) {
312 recursivelyExpand(start.parentElement)
317 search.onTermSelection = (docId: string) => {
318 // if any parent element in the chain to this node
319 // are collapsed, we want to make sure we expand them
321 recursivelyExpand(document.getElementById(`id-${docId}`).parentElement);
322 cursor.set(`#id-${docId}`);
327 function saveImmediate() {
328 localStorage.setItem(outline.data.id, JSON.stringify(outline.data));
329 localStorage.setItem('activeOutline', outline.data.id);
330 console.log('saved...', outline.data);
331 state.delete('saveTimeout');
335 if(!state.has('saveTimeout')) {
336 state.set('saveTimeout', setTimeout(saveImmediate, 2000));