Skip to content

Commit

Permalink
refactor: extract listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
sorax committed Mar 17, 2024
1 parent aae9e35 commit 82201d4
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 176 deletions.
43 changes: 43 additions & 0 deletions assets/js/hooks/event_handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Node } from "./types"
import { createItem, updateItem, deleteItem, focusItem } from "./item"

export function handleList({ nodes }: { nodes: Node[] }) {
const container: HTMLOListElement = this.el

if ((nodes.length) == 0) {
const node: Node = { temp_id: self.crypto.randomUUID(), content: "" }
nodes = [node]
}

// add all items
nodes.forEach(node => {
const item = createItem(node)
container.append(item)
})

// sort & indent all items
nodes.forEach(node => {
updateItem(node, container)
})

// focus last item
const lastItem = container.lastElementChild as HTMLLIElement
focusItem(lastItem)
}

export function handleInsert(node: Node) {
const container: HTMLOListElement = this.el

const item = createItem(node)
container.append(item)
}

export function handleUpdate(node: Node) {
const container: HTMLOListElement = this.el

updateItem(node, container)
}

export function handleDelete(node: Node) {
deleteItem(node)
}
144 changes: 144 additions & 0 deletions assets/js/hooks/event_listener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Node } from "./types"
import { createItem, updateItem, deleteItem, getItemByNode, focusItem } from "./item"
import { getNodeByEvent, getNodeByItem } from "./node"

export function focusin(event: FocusEvent) {
const { uuid } = getNodeByEvent(event)

this.pushEvent("set_focus", uuid)
}

export function focusout(event: FocusEvent) {
const { uuid } = getNodeByEvent(event)

this.pushEvent("remove_focus", uuid)
}

export function input(event: Event) {
const node = getNodeByEvent(event)

this.pushEvent("update_node", node)
}

export function keydown(event: KeyboardEvent) {
const container: HTMLOListElement = this.el

const selection = window.getSelection()
// const range = selection?.getRangeAt(0)

const node = getNodeByEvent(event)

const item = getItemByNode(node)!
const prevItem = item.previousSibling as HTMLLIElement | null
const nextItem = item.nextSibling as HTMLLIElement | null

const prevNode = prevItem && getNodeByItem(prevItem)
const nextNode = nextItem && getNodeByItem(nextItem)

switch (event.key) {
case "ArrowUp":
if (selection?.anchorOffset != 0) return
event.preventDefault()

if (!prevItem || !prevNode) return
// TODO: if no prevItem exists, try to select the parent item

focusItem(prevItem)
this.pushEvent("set_focus", prevNode.uuid)
break

case "ArrowDown":
if (selection?.anchorOffset != node.content.length) return
event.preventDefault()

if (!nextItem || !nextNode) return
// TODO: if no nextItem exists, try to select the first child

focusItem(nextItem)
this.pushEvent("set_focus", nextNode.uuid)
break

case "Enter":
event.preventDefault()

const splitPos = selection?.anchorOffset || 0

const content = node.content
node.content = content?.substring(0, splitPos)

updateItem(node, container)
this.pushEvent("update_node", node)

const newNode: Node = {
temp_id: self.crypto.randomUUID(),
content: content?.substring(splitPos),
parent_id: node.parent_id,
prev_id: node.uuid
}

this.pushEvent("create_node", newNode, (node: Node, _ref: number) => {
const newItem = createItem(node)
item.after(newItem)
focusItem(newItem, false)
})
break

case "Backspace":
if (selection?.anchorOffset != 0) return
event.preventDefault()

if (!prevItem || !prevNode) return

prevNode.content += node.content
updateItem(prevNode, container)
focusItem(prevItem)
this.pushEvent("update_node", node)

deleteItem(node)
this.pushEvent("delete_node", node.uuid)
break

case "Delete":
if (selection?.anchorOffset != node.content.length) return
event.preventDefault()

if (!nextItem || !nextNode) return

node.content += nextNode.content
updateItem(node, container)
focusItem(item)
this.pushEvent("update_node", node)

deleteItem(nextNode)
this.pushEvent("delete_node", nextNode.uuid)
break

case "Tab":
event.preventDefault()

if (event.shiftKey) {
if (node.parent_id) {
// outdent
node.prev_id = node.parent_id
node.parent_id = undefined
updateItem(node, container)

focusItem(item)
this.pushEvent("update_node", node)
}
} else {
if (node.prev_id) {
// indent
node.parent_id = node.prev_id
node.prev_id = undefined // TODO: prev_id should be the id of the last child of the parent node
updateItem(node, container)

focusItem(item)
this.pushEvent("update_node", node)
}
}
break
}
}


187 changes: 11 additions & 176 deletions assets/js/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,187 +1,22 @@
import { Node } from "./types"
import { createItem, updateItem, deleteItem, getItemByNode, focusItem } from "./item"
import { getNodeByEvent, getNodeByItem } from "./node"
import { focusin, focusout, input, keydown } from "./event_listener"
import { handleList, handleInsert, handleUpdate, handleDelete } from "./event_handler"

export const Hooks = {
outline: {
mounted() {
const container: HTMLOListElement = this.el

container.addEventListener("focusin", (event: FocusEvent) => {
const { uuid } = getNodeByEvent(event)
container.addEventListener("focusin", focusin.bind(this))
container.addEventListener("focusout", focusout.bind(this))
container.addEventListener("input", input.bind(this))

this.pushEvent("set_focus", uuid)
})
container.addEventListener("keydown", keydown.bind(this))
// container.addEventListener("keyup", keyup.bind(this))

container.addEventListener("focusout", (event: FocusEvent) => {
const { uuid } = getNodeByEvent(event)

this.pushEvent("remove_focus", uuid)
})

container.addEventListener("input", (event: Event) => {
const node = getNodeByEvent(event)

this.pushEvent("update_node", node)
})

container.addEventListener("keydown", (event: KeyboardEvent) => {
const selection = window.getSelection()
// const range = selection?.getRangeAt(0)

const node = getNodeByEvent(event)

const item = getItemByNode(node)!
const prevItem = item.previousSibling as HTMLLIElement | null
const nextItem = item.nextSibling as HTMLLIElement | null

const prevNode = prevItem && getNodeByItem(prevItem)
const nextNode = nextItem && getNodeByItem(nextItem)

switch (event.key) {
case "ArrowUp":
if (selection?.anchorOffset != 0) return
event.preventDefault()

if (!prevItem || !prevNode) return
// TODO: if no prevItem exists, try to select the parent item

focusItem(prevItem)
this.pushEvent("set_focus", prevNode.uuid)
break

case "ArrowDown":
if (selection?.anchorOffset != node.content.length) return
event.preventDefault()

if (!nextItem || !nextNode) return
// TODO: if no nextItem exists, try to select the first child

focusItem(nextItem)
this.pushEvent("set_focus", nextNode.uuid)
break

case "Enter":
event.preventDefault()

const splitPos = selection?.anchorOffset || 0

const content = node.content
node.content = content?.substring(0, splitPos)

updateItem(node, container)
this.pushEvent("update_node", node)

const newNode: Node = {
temp_id: self.crypto.randomUUID(),
content: content?.substring(splitPos),
parent_id: node.parent_id,
prev_id: node.uuid
}

this.pushEvent("create_node", newNode, (node: Node, _ref: number) => {
const newItem = createItem(node)
item.after(newItem)
focusItem(newItem, false)
})
break

case "Backspace":
if (selection?.anchorOffset != 0) return
event.preventDefault()

if (!prevItem || !prevNode) return

prevNode.content += node.content
updateItem(prevNode, container)
focusItem(prevItem)
this.pushEvent("update_node", node)

deleteItem(node)
this.pushEvent("delete_node", node.uuid)
break

case "Delete":
if (selection?.anchorOffset != node.content.length) return
event.preventDefault()

if (!nextItem || !nextNode) return

node.content += nextNode.content
updateItem(node, container)
focusItem(item)
this.pushEvent("update_node", node)

deleteItem(nextNode)
this.pushEvent("delete_node", nextNode.uuid)
break

case "Tab":
event.preventDefault()

if (event.shiftKey) {
if (node.parent_id) {
// outdent
node.prev_id = node.parent_id
node.parent_id = undefined
updateItem(node, container)

focusItem(item)
this.pushEvent("update_node", node)
}
} else {
if (node.prev_id) {
// indent
node.parent_id = node.prev_id
node.prev_id = undefined // TODO: prev_id should be the id of the last child of the parent node
updateItem(node, container)

focusItem(item)
this.pushEvent("update_node", node)
}
}
break
}
})

// container.addEventListener("keyup", (event) => {
// console.log("keyup", event)
// })

this.handleEvent("list", ({ nodes }: { nodes: Node[] }) => {
if ((nodes.length) == 0) {
const node: Node = { temp_id: self.crypto.randomUUID(), content: "" }
nodes = [node]
}

// add all items
nodes.forEach(node => {
const item = createItem(node)
container.append(item)
})

// sort & indent all items
nodes.forEach(node => {
updateItem(node, container)
})

// focus last item
const lastItem = container.lastElementChild as HTMLLIElement
focusItem(lastItem)
})

this.handleEvent("insert", (node: Node) => {
const item = createItem(node)
container.append(item)
})

this.handleEvent("update", (node: Node) => {
updateItem(node, container)
})

this.handleEvent("delete", (node: Node) => {
deleteItem(node)
})
this.handleEvent("list", handleList.bind(this))
this.handleEvent("insert", handleInsert.bind(this))
this.handleEvent("update", handleUpdate.bind(this))
this.handleEvent("delete", handleDelete.bind(this))
}
}
}

0 comments on commit 82201d4

Please sign in to comment.