1 import { Outline, RawOutline } from './outline';
2 import { Cursor } from './cursor';
3 import keyboardJS from 'keyboardjs';
4 import * as rawOutline from './test-data.json';
6 let outlineData = rawOutline;
7 if(localStorage.getItem('activeOutline')) {
8 const outlineId = localStorage.getItem('activeOutline');
9 outlineData = JSON.parse(localStorage.getItem(outlineId));
12 const state = new Map<string, any>();
13 const outline = new Outline(outlineData as unknown as RawOutline);
14 outliner().innerHTML = outline.render();
16 const cursor = new Cursor();
17 // place the cursor at the top!
21 return document.querySelector('#outliner');
25 keyboardJS.withContext('navigation', () => {
26 keyboardJS.bind('j', e => {
28 // if shift key is held, swap the node with its next sibling
29 const sibling = cursor.get().nextElementSibling;
33 // swap this node with its previous sibling
34 const res = outline.swapNodeWithNextSibling(cursor.getIdOfNode());
35 const html = outline.renderNode(res.parentNode);
37 if(res.parentNode.id === '000000') {
38 cursor.get().parentElement.innerHTML = html;
41 cursor.get().parentElement.outerHTML = html;
44 cursor.set(`#id-${res.targetNode.id}`);
48 cursor.set(`#id-${sibling.getAttribute('data-id')}`);
53 keyboardJS.bind('k', e => {
55 // if shift key is held, swap the node with its previous sibling
56 const sibling = cursor.get().previousElementSibling;
58 if(sibling && !sibling.classList.contains('nodeContent')) {
60 // swap this node with its previous sibling
61 const res = outline.swapNodeWithPreviousSibling(cursor.getIdOfNode());
62 // re-render the parent node and display that!
63 const html = outline.renderNode(res.parentNode);
65 if(res.parentNode.id === '000000') {
66 cursor.get().parentElement.innerHTML = html;
69 cursor.get().parentElement.outerHTML = html;
72 cursor.set(`#id-${res.targetNode.id}`);
76 cursor.set(`#id-${sibling.getAttribute('data-id')}`);
81 keyboardJS.bind('l', e => {
82 // if the node is collapsed, we can't go into its children
83 if(cursor.isNodeCollapsed()) {
87 const res = outline.lowerNodeToChild(cursor.getIdOfNode());
88 const html = outline.renderNode(res.oldParentNode);
90 if(res.oldParentNode.id === '000000') {
91 cursor.get().parentElement.innerHTML = html;
94 cursor.get().parentElement.outerHTML = html;
96 cursor.set(`#id-${res.targetNode.id}`);
99 const children = cursor.get().querySelector('.node');
101 cursor.set(`#id-${children.getAttribute('data-id')}`);
106 keyboardJS.bind('h', e => {
107 const parent = cursor.get().parentElement;
108 if(parent && parent.classList.contains('node')) {
110 if(outline.data.tree.children.map(n => n.id).includes(cursor.getIdOfNode())) {
111 // if this is a top level item, we can't elevate any further
114 const res = outline.liftNodeToParent(cursor.getIdOfNode());
116 const html = outline.renderNode(res.parentNode);
118 if(res.parentNode.id === '000000') {
119 cursor.get().parentElement.parentElement.innerHTML = html;
122 cursor.get().parentElement.parentElement.outerHTML = html;
125 cursor.set(`#id-${res.targetNode.id}`);
129 cursor.set(`#id-${parent.getAttribute('data-id')}`);
134 keyboardJS.bind('z', e => {
136 if(cursor.isNodeExpanded()) {
138 outline.fold(cursor.getIdOfNode());
140 else if(cursor.isNodeCollapsed()) {
142 outline.unfold(cursor.getIdOfNode());
147 keyboardJS.bind('i', e => {
149 // switch to editing mode
150 cursor.get().classList.add('hidden-cursor');
151 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
153 // swap the content to the default!
154 contentNode.innerHTML = outline.data.contentNodes[cursor.getIdOfNode()].content;
155 contentNode.contentEditable = "true";
157 keyboardJS.setContext('editing');
160 keyboardJS.bind('tab', e => {
163 const res = outline.createChildNode(cursor.getIdOfNode());
164 const html = outline.renderNode(res.parentNode);
166 cursor.get().outerHTML = html;
168 cursor.set(`#id-${res.node.id}`);
172 keyboardJS.bind('enter', e => {
173 // create a new node as a sibling of the selected node
180 const res = outline.createSiblingNode(cursor.getIdOfNode());
182 const html = outline.renderNode(res.parentNode);
183 if(res.parentNode.id === '000000') {
184 cursor.get().parentElement.innerHTML = html;
187 cursor.get().parentElement.outerHTML = html;
190 cursor.set(`#id-${res.node.id}`);
194 keyboardJS.bind('d', e => {
195 // deleting a node requires d + shift
200 const res = outline.removeNode(cursor.getIdOfNode());
201 const html = outline.renderNode(res.parentNode);
202 // the previous sibling!
203 const prevSibling = cursor.get().previousElementSibling;
204 const nextSibling = cursor.get().nextElementSibling;
205 if(res.parentNode.id === '000000') {
206 cursor.get().parentElement.innerHTML = html;
209 cursor.get().parentElement.outerHTML = html;
212 if(prevSibling.getAttribute('data-id')) {
213 cursor.set(`#id-${prevSibling.getAttribute('data-id')}`);
215 else if(nextSibling.getAttribute('data-id')) {
216 cursor.set(`#id-${nextSibling.getAttribute('data-id')}`);
219 console.log(res.parentNode.id);
220 cursor.set(`#id-${res.parentNode.id}`);
227 keyboardJS.withContext('editing', () => {
228 keyboardJS.bind(['esc', 'enter'], e => {
229 cursor.get().classList.remove('hidden-cursor');
231 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
233 contentNode.contentEditable = "false";
235 keyboardJS.setContext('navigation');
237 outline.updateContent(cursor.getIdOfNode(), contentNode.innerHTML.trim());
238 // re-render this node!
239 contentNode.innerHTML = outline.renderContent(cursor.getIdOfNode());
244 keyboardJS.setContext('navigation');
246 function saveImmediate() {
247 localStorage.setItem(outline.data.id, JSON.stringify(outline.data));
248 localStorage.setItem('activeOutline', outline.data.id);
249 console.log('saved...', outline.data);
250 state.delete('saveTimeout');
254 if(!state.has('saveTimeout')) {
255 state.set('saveTimeout', setTimeout(saveImmediate, 2000));