Skip to content

Commit

Permalink
Text Editor: Save and load drafts as markdown via localStorage
Browse files Browse the repository at this point in the history
- Add support for tying localStorage keys to session
- Ensure prototypes are applied first
- Make TextEditor more navigable with more regions and ctrl+clickable vars & types within each region
- Add support for strikethrough, underline, and text align markdown parse/serialise
- Make autolabel smarter when used in named forms to ensure that text editor names stay the same (and thereby can be saved & loaded)
- Ensure `name` attribute is set to valid text
- Fix text editor not having white-space: pre-wrap
- Fix async import() not working
- Fix slots not running content cleanup function on remove
  • Loading branch information
ChiriVulpes committed Oct 28, 2024
1 parent ef7df00 commit 6ccb686
Show file tree
Hide file tree
Showing 11 changed files with 652 additions and 64 deletions.
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import App from "App"
import Arrays from "utility/Arrays"
import applyDOMRectPrototypes from "utility/DOMRect"
import Elements from "utility/Elements"
Expand All @@ -19,4 +18,5 @@ document.startViewTransition ??= cb => {
applyDOMRectPrototypes()
Arrays.applyPrototypes()
Elements.applyPrototypes()
void App()

void import("App").then(app => app.default())
11 changes: 11 additions & 0 deletions src/model/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import EndpointSessionGet from "endpoint/session/EndpointSessionGet"
import EndpointSessionReset from "endpoint/session/EndpointSessionReset"
import popup from "utility/Popup"
import State from "utility/State"
import type { ILocalStorage } from "utility/Store"
import Store from "utility/Store"

declare module "utility/Store" {
Expand All @@ -15,12 +16,22 @@ declare module "utility/Store" {
}

namespace Session {

const clearedWithSessionChange: (keyof ILocalStorage)[] = []
export function setClearedWithSessionChange (...keys: (keyof ILocalStorage)[]) {
clearedWithSessionChange.push(...keys)
}

export async function refresh () {
const session = await EndpointSessionGet.query()
const stateToken = session.headers.get("State-Token")
if (stateToken)
Store.items.stateToken = stateToken

if (Store.items.session?.created !== session.data?.created)
for (const key of clearedWithSessionChange)
Store.delete(key)

Store.items.session = session?.data ?? undefined
updateState()
}
Expand Down
1 change: 1 addition & 0 deletions src/ui/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ function Component (type: keyof HTMLElementTagNameMap = "span"): Component {

function setName (name?: string) {
if (name) {
name = name.replace(/[^\w-]+/g, "-").toLowerCase()
component.element.setAttribute("name", name)
component.name.value = name
} else {
Expand Down
36 changes: 32 additions & 4 deletions src/ui/component/core/Label.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { ComponentBrand } from "ui/Component"
import Component from "ui/Component"
import type Input from "ui/component/core/extension/Input"
import Form from "ui/component/core/Form"
import View from "ui/view/View"
import type { UnsubscribeState } from "utility/State"
import State from "utility/State"

interface LabelExtensions {
Expand Down Expand Up @@ -53,11 +56,36 @@ export interface AutoLabel extends Label, AutoLabelExtensions { }

let globalI = 0
export const AutoLabel = Component.Builder("label", (component): AutoLabel => {
const i = globalI++

const label = component.and(Label)

const i = globalI++
label.text.state.use(label, text => label.setFor(`${text.toString().toLowerCase().replace(/\W+/g, "-")}-${i}`))
let formName: string | undefined
let viewPath: string | undefined

let unuseFormName: UnsubscribeState | undefined

label.receiveAncestorInsertEvents()
label.event.subscribe(["insert", "ancestorInsert"], () => {
unuseFormName?.()

const form = label.closest(Form)
unuseFormName = form?.name.use(label, name => formName = name)

const view = label.closest(View)
viewPath = view ? location.pathname.slice(1).replaceAll("/", "-") : "_"

updateFor()
})
label.text.state.use(label, () => updateFor())

return label.extend<AutoLabelExtensions>(label => ({}))

return label.extend<AutoLabelExtensions>(label => ({
}))
function updateFor () {
const text = label.text.state.value.toString().toLowerCase().replace(/\W+/g, "-")
if (!formName)
label.setFor(`${viewPath}--${text}--${i}`)
else
label.setFor(`${viewPath}--${formName}--${text}`)
}
})
22 changes: 12 additions & 10 deletions src/ui/component/core/Slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ const Slot = Component.Builder((slot): Slot => {
slot.style("slot")

let cleanup: SlotCleanup | undefined
return slot.extend<SlotExtensions>(slot => ({
use: (state, initialiser) => {
state.use(slot, value => {
cleanup?.()
slot.removeContents()
cleanup = initialiser(slot, value) ?? undefined
})
return slot
.extend<SlotExtensions>(slot => ({
use: (state, initialiser) => {
state.use(slot, value => {
cleanup?.()
slot.removeContents()
cleanup = initialiser(slot, value) ?? undefined
})

return slot
},
}))
return slot
},
}))
.event.subscribe("remove", () => cleanup?.())
})

export default Slot
Loading

0 comments on commit 6ccb686

Please sign in to comment.