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('shift + 4', 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 const range = document.createRange();
158 range.selectNodeContents(contentNode);
159 range.collapse(false);
161 const selection = window.getSelection();
162 selection.removeAllRanges();
163 selection.addRange(range);
166 keyboardJS.setContext('editing');
169 keyboardJS.bind('i', e => {
171 // switch to editing mode
172 cursor.get().classList.add('hidden-cursor');
173 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
175 // swap the content to the default!
176 contentNode.innerHTML = outline.data.contentNodes[cursor.getIdOfNode()].content;
177 contentNode.contentEditable = "true";
179 keyboardJS.setContext('editing');
182 keyboardJS.bind('tab', e => {
185 const res = outline.createChildNode(cursor.getIdOfNode());
186 const html = outline.renderNode(res.parentNode);
188 cursor.get().outerHTML = html;
190 cursor.set(`#id-${res.node.id}`);
194 keyboardJS.bind('enter', e => {
195 // create a new node as a sibling of the selected node
202 const res = outline.createSiblingNode(cursor.getIdOfNode());
204 const html = outline.renderNode(res.parentNode);
205 if(res.parentNode.id === '000000') {
206 cursor.get().parentElement.innerHTML = html;
209 cursor.get().parentElement.outerHTML = html;
212 cursor.set(`#id-${res.node.id}`);
216 keyboardJS.bind('d', e => {
217 // deleting a node requires d + shift
222 const res = outline.removeNode(cursor.getIdOfNode());
223 const html = outline.renderNode(res.parentNode);
224 // the previous sibling!
225 const prevSibling = cursor.get().previousElementSibling;
226 const nextSibling = cursor.get().nextElementSibling;
227 if(res.parentNode.id === '000000') {
228 cursor.get().parentElement.innerHTML = html;
231 cursor.get().parentElement.outerHTML = html;
234 if(prevSibling.getAttribute('data-id')) {
235 cursor.set(`#id-${prevSibling.getAttribute('data-id')}`);
237 else if(nextSibling.getAttribute('data-id')) {
238 cursor.set(`#id-${nextSibling.getAttribute('data-id')}`);
241 console.log(res.parentNode.id);
242 cursor.set(`#id-${res.parentNode.id}`);
249 keyboardJS.withContext('editing', () => {
250 keyboardJS.bind(['esc', 'enter'], e => {
251 cursor.get().classList.remove('hidden-cursor');
253 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
255 contentNode.contentEditable = "false";
257 keyboardJS.setContext('navigation');
259 outline.updateContent(cursor.getIdOfNode(), contentNode.innerHTML.trim());
260 // re-render this node!
261 contentNode.innerHTML = outline.renderContent(cursor.getIdOfNode());
266 keyboardJS.setContext('navigation');
268 function saveImmediate() {
269 localStorage.setItem(outline.data.id, JSON.stringify(outline.data));
270 localStorage.setItem('activeOutline', outline.data.id);
271 console.log('saved...', outline.data);
272 state.delete('saveTimeout');
276 if(!state.has('saveTimeout')) {
277 state.set('saveTimeout', setTimeout(saveImmediate, 2000));