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';
7 let outlineData = rawOutline;
8 if(localStorage.getItem('activeOutline')) {
9 const outlineId = localStorage.getItem('activeOutline');
10 outlineData = JSON.parse(localStorage.getItem(outlineId));
13 const state = new Map<string, any>();
14 const outline = new Outline(outlineData as unknown as RawOutline);
15 outliner().innerHTML = outline.render();
17 const cursor = new Cursor();
18 // place the cursor at the top!
22 return document.querySelector('#outliner');
25 document.getElementById('display-help').addEventListener('click', e => {
33 keyboardJS.withContext('navigation', () => {
34 keyboardJS.bind('j', e => {
36 // if shift key is held, swap the node with its next sibling
37 const sibling = cursor.get().nextElementSibling;
41 // swap this node with its previous sibling
42 const res = outline.swapNodeWithNextSibling(cursor.getIdOfNode());
43 const html = outline.renderNode(res.parentNode);
45 if(res.parentNode.id === '000000') {
46 cursor.get().parentElement.innerHTML = html;
49 cursor.get().parentElement.outerHTML = html;
52 cursor.set(`#id-${res.targetNode.id}`);
56 cursor.set(`#id-${sibling.getAttribute('data-id')}`);
62 keyboardJS.bind('shift + /', e => {
66 keyboardJS.bind('k', e => {
68 // if shift key is held, swap the node with its previous sibling
69 const sibling = cursor.get().previousElementSibling;
71 if(sibling && !sibling.classList.contains('nodeContent')) {
73 // swap this node with its previous sibling
74 const res = outline.swapNodeWithPreviousSibling(cursor.getIdOfNode());
75 // re-render the parent node and display that!
76 const html = outline.renderNode(res.parentNode);
78 if(res.parentNode.id === '000000') {
79 cursor.get().parentElement.innerHTML = html;
82 cursor.get().parentElement.outerHTML = html;
85 cursor.set(`#id-${res.targetNode.id}`);
89 cursor.set(`#id-${sibling.getAttribute('data-id')}`);
94 keyboardJS.bind('l', e => {
95 // if the node is collapsed, we can't go into its children
96 if(cursor.isNodeCollapsed()) {
100 const res = outline.lowerNodeToChild(cursor.getIdOfNode());
101 const html = outline.renderNode(res.oldParentNode);
103 if(res.oldParentNode.id === '000000') {
104 cursor.get().parentElement.innerHTML = html;
107 cursor.get().parentElement.outerHTML = html;
109 cursor.set(`#id-${res.targetNode.id}`);
112 const children = cursor.get().querySelector('.node');
114 cursor.set(`#id-${children.getAttribute('data-id')}`);
119 keyboardJS.bind('h', e => {
120 const parent = cursor.get().parentElement;
121 if(parent && parent.classList.contains('node')) {
123 if(outline.data.tree.children.map(n => n.id).includes(cursor.getIdOfNode())) {
124 // if this is a top level item, we can't elevate any further
127 const res = outline.liftNodeToParent(cursor.getIdOfNode());
129 const html = outline.renderNode(res.parentNode);
131 if(res.parentNode.id === '000000') {
132 cursor.get().parentElement.parentElement.innerHTML = html;
135 cursor.get().parentElement.parentElement.outerHTML = html;
138 cursor.set(`#id-${res.targetNode.id}`);
142 cursor.set(`#id-${parent.getAttribute('data-id')}`);
147 keyboardJS.bind('z', e => {
149 if(cursor.isNodeExpanded()) {
151 outline.fold(cursor.getIdOfNode());
153 else if(cursor.isNodeCollapsed()) {
155 outline.unfold(cursor.getIdOfNode());
160 keyboardJS.bind('shift + 4', e => {
162 // switch to editing mode
163 cursor.get().classList.add('hidden-cursor');
164 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
166 // swap the content to the default!
167 contentNode.innerHTML = outline.data.contentNodes[cursor.getIdOfNode()].content;
168 contentNode.contentEditable = "true";
170 const range = document.createRange();
171 range.selectNodeContents(contentNode);
172 range.collapse(false);
174 const selection = window.getSelection();
175 selection.removeAllRanges();
176 selection.addRange(range);
179 keyboardJS.setContext('editing');
182 keyboardJS.bind('i', e => {
184 // switch to editing mode
185 cursor.get().classList.add('hidden-cursor');
186 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
188 // swap the content to the default!
189 contentNode.innerHTML = outline.data.contentNodes[cursor.getIdOfNode()].content;
190 contentNode.contentEditable = "true";
192 keyboardJS.setContext('editing');
195 keyboardJS.bind('tab', e => {
198 const res = outline.createChildNode(cursor.getIdOfNode());
199 const html = outline.renderNode(res.parentNode);
201 cursor.get().outerHTML = html;
203 cursor.set(`#id-${res.node.id}`);
207 keyboardJS.bind('enter', e => {
208 // create a new node as a sibling of the selected node
215 const res = outline.createSiblingNode(cursor.getIdOfNode());
217 const html = outline.renderNode(res.parentNode);
218 if(res.parentNode.id === '000000') {
219 cursor.get().parentElement.innerHTML = html;
222 cursor.get().parentElement.outerHTML = html;
225 cursor.set(`#id-${res.node.id}`);
229 keyboardJS.bind('d', e => {
230 // deleting a node requires d + shift
235 const res = outline.removeNode(cursor.getIdOfNode());
236 const html = outline.renderNode(res.parentNode);
237 // the previous sibling!
238 const prevSibling = cursor.get().previousElementSibling;
239 const nextSibling = cursor.get().nextElementSibling;
240 if(res.parentNode.id === '000000') {
241 cursor.get().parentElement.innerHTML = html;
244 cursor.get().parentElement.outerHTML = html;
247 if(prevSibling.getAttribute('data-id')) {
248 cursor.set(`#id-${prevSibling.getAttribute('data-id')}`);
250 else if(nextSibling.getAttribute('data-id')) {
251 cursor.set(`#id-${nextSibling.getAttribute('data-id')}`);
254 console.log(res.parentNode.id);
255 cursor.set(`#id-${res.parentNode.id}`);
262 keyboardJS.withContext('editing', () => {
263 keyboardJS.bind(['esc', 'enter'], e => {
264 cursor.get().classList.remove('hidden-cursor');
266 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
268 contentNode.contentEditable = "false";
270 keyboardJS.setContext('navigation');
272 outline.updateContent(cursor.getIdOfNode(), contentNode.innerHTML.trim());
273 // re-render this node!
274 contentNode.innerHTML = outline.renderContent(cursor.getIdOfNode());
279 keyboardJS.setContext('navigation');
281 function saveImmediate() {
282 localStorage.setItem(outline.data.id, JSON.stringify(outline.data));
283 localStorage.setItem('activeOutline', outline.data.id);
284 console.log('saved...', outline.data);
285 state.delete('saveTimeout');
289 if(!state.has('saveTimeout')) {
290 state.set('saveTimeout', setTimeout(saveImmediate, 2000));