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('shift + x', e => {
197 // toggle "strikethrough" of node
198 cursor.get().classList.toggle('strikethrough');
199 outline.data.contentNodes[cursor.getIdOfNode()].strikethrough = cursor.get().classList.contains('strikethrough');
203 keyboardJS.bind('tab', e => {
206 const res = outline.createChildNode(cursor.getIdOfNode());
207 const html = outline.renderNode(res.parentNode);
209 cursor.get().outerHTML = html;
211 cursor.set(`#id-${res.node.id}`);
215 keyboardJS.bind('enter', e => {
216 // create a new node as a sibling of the selected node
223 const res = outline.createSiblingNode(cursor.getIdOfNode());
225 const html = outline.renderNode(res.parentNode);
226 if(res.parentNode.id === '000000') {
227 cursor.get().parentElement.innerHTML = html;
230 cursor.get().parentElement.outerHTML = html;
233 cursor.set(`#id-${res.node.id}`);
237 keyboardJS.bind('d', e => {
238 // deleting a node requires d + shift
243 const res = outline.removeNode(cursor.getIdOfNode());
244 const html = outline.renderNode(res.parentNode);
245 // the previous sibling!
246 const prevSibling = cursor.get().previousElementSibling;
247 const nextSibling = cursor.get().nextElementSibling;
248 if(res.parentNode.id === '000000') {
249 cursor.get().parentElement.innerHTML = html;
252 cursor.get().parentElement.outerHTML = html;
255 if(prevSibling.getAttribute('data-id')) {
256 cursor.set(`#id-${prevSibling.getAttribute('data-id')}`);
258 else if(nextSibling.getAttribute('data-id')) {
259 cursor.set(`#id-${nextSibling.getAttribute('data-id')}`);
262 console.log(res.parentNode.id);
263 cursor.set(`#id-${res.parentNode.id}`);
270 keyboardJS.withContext('editing', () => {
271 keyboardJS.bind(['esc', 'enter'], e => {
272 cursor.get().classList.remove('hidden-cursor');
274 const contentNode = cursor.get().querySelector('.nodeContent') as HTMLElement;
276 contentNode.contentEditable = "false";
278 keyboardJS.setContext('navigation');
280 outline.updateContent(cursor.getIdOfNode(), contentNode.innerHTML.trim());
281 // re-render this node!
282 contentNode.innerHTML = outline.renderContent(cursor.getIdOfNode());
287 keyboardJS.setContext('navigation');
289 function saveImmediate() {
290 localStorage.setItem(outline.data.id, JSON.stringify(outline.data));
291 localStorage.setItem('activeOutline', outline.data.id);
292 console.log('saved...', outline.data);
293 state.delete('saveTimeout');
297 if(!state.has('saveTimeout')) {
298 state.set('saveTimeout', setTimeout(saveImmediate, 2000));