A much better way to handle links
This commit is contained in:
parent
331a29eb4a
commit
3abd4447b2
|
|
@ -20,14 +20,9 @@ const buildDynamicRoute = async ({ distDir, routeName, filepath }: BuildRouteOpt
|
|||
const dynamicRouteFilepath = join(distDir, "routes", outDir, filename)
|
||||
await mkdirSync(dirname(dynamicRouteFilepath), { recursive: true })
|
||||
|
||||
// Create a relative import path from the generated file to the source file
|
||||
const relativeImportPath = relative(dirname(dynamicRouteFilepath), filepath)
|
||||
// Normalize the path for cross-platform compatibility and ensure forward slashes
|
||||
const normalizedImportPath = relativeImportPath.replace(/\\/g, "/")
|
||||
|
||||
// Only import the Component so that tree-shaking will get rid of the server-side code
|
||||
const code = `
|
||||
import Component from "${normalizedImportPath}"
|
||||
import Component from "${filepath}"
|
||||
import { wrapComponentWithLoader} from "@workshop/nano-remix"
|
||||
import { render } from 'hono/jsx/dom'
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,12 @@ type BuildRouteOptions = {
|
|||
|
||||
export const buildRoute = async ({ distDir, routeName, filepath, force = false }: BuildRouteOptions) => {
|
||||
if (!force && !(await shouldRebuild(routeName, filepath, distDir))) {
|
||||
console.log(`🌭 Skipping build for ${routeName} - up to date`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`🌭 Building route ${routeName} from ${filepath}`)
|
||||
|
||||
const scriptPath = join(import.meta.dirname, "../scripts/build.ts")
|
||||
|
||||
const proc = Bun.spawn({
|
||||
|
|
@ -32,6 +35,10 @@ export const buildRoute = async ({ distDir, routeName, filepath, force = false }
|
|||
}
|
||||
|
||||
const shouldRebuild = async (routeName: string, sourceFilePath: string, distDir: string) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const outputPath = join(distDir, routeName + ".js")
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,7 @@ export const nanoRemix = async (req: Request, options: Options = {}) => {
|
|||
|
||||
// If the the route includes an extension it is a static file that we serve from the distDir
|
||||
if (!ext) {
|
||||
await buildRoute({
|
||||
distDir,
|
||||
routeName,
|
||||
filepath: route.filePath,
|
||||
force: options.disableCache, // Force rebuild if cache is disabled
|
||||
})
|
||||
await buildRoute({ distDir, routeName, filepath: route.filePath, force: options.disableCache })
|
||||
return await renderServer(req, route)
|
||||
} else {
|
||||
const file = Bun.file(join(distDir, routeName + ext))
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "bun run src/server.tsx",
|
||||
"serve-subdomain": "NODE_ENV=production bun run src/server.tsx"
|
||||
"serve-subdomain": "bun run src/server.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { triggerTodoCompletionEffect } from "./todoCompletion"
|
|||
|
||||
export const todoClickHandler = EditorView.domEventHandlers({
|
||||
click(event, view) {
|
||||
const element = event.target as HTMLElement
|
||||
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY })
|
||||
if (pos === null) return false
|
||||
|
||||
|
|
@ -12,40 +13,26 @@ export const todoClickHandler = EditorView.domEventHandlers({
|
|||
|
||||
if (!todo) return false
|
||||
|
||||
// Find the checkbox position in the line
|
||||
const checkboxMatch = line.text.match(/^(\s*-\s*\[)\s*(\w?)\s*(\]\s+)/)
|
||||
if (!checkboxMatch) return false
|
||||
if (element.matches(".todo-url")) {
|
||||
const url = element.getAttribute("data-url")
|
||||
if (url) {
|
||||
window.open(url, "_blank")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const [fullMatch, beforeCheckbox, currentState, afterCheckbox] = checkboxMatch
|
||||
if (!beforeCheckbox || !afterCheckbox) return false
|
||||
|
||||
const checkboxStart = line.from + beforeCheckbox.length
|
||||
const checkboxEnd = checkboxStart + 1
|
||||
|
||||
// Check if the click was within the checkbox area (including some padding)
|
||||
const clickOffset = pos - line.from
|
||||
|
||||
// Allow clicking anywhere in the "- [ ]" or "- [x]" part
|
||||
if (clickOffset >= 0 && clickOffset <= fullMatch.length) {
|
||||
// Toggle the checkbox state
|
||||
const newState = todo.done ? " " : "x"
|
||||
const isCompleting = !todo.done // Will be completing if currently not done
|
||||
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: checkboxStart,
|
||||
to: checkboxEnd,
|
||||
insert: newState,
|
||||
},
|
||||
})
|
||||
|
||||
// Add animation effect when completing (not uncompleting)
|
||||
if (isCompleting) {
|
||||
if (element.matches(".todo-checkbox")) {
|
||||
todo.done = !todo.done
|
||||
if (todo.done) {
|
||||
triggerTodoCompletionEffect(view, line.from)
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
return true
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: line.from,
|
||||
to: line.to,
|
||||
insert: todo.toString(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ import { completionAnimationEffect } from "@/todoDecorations"
|
|||
import { EditorView } from "@codemirror/view"
|
||||
|
||||
export const triggerTodoCompletionEffect = async (view: EditorView, pos: number) => {
|
||||
console.log(`🌭 HAHAHA`, pos)
|
||||
|
||||
// Dispatch the completion animation effect
|
||||
view.dispatch({
|
||||
effects: completionAnimationEffect.of({ pos, duration: 1000 }),
|
||||
|
|
|
|||
|
|
@ -2,25 +2,6 @@ import { Todo } from "@/todo"
|
|||
import { RangeSetBuilder, StateEffect } from "@codemirror/state"
|
||||
import { EditorView, Decoration, ViewPlugin, ViewUpdate, WidgetType } from "@codemirror/view"
|
||||
import { type RefObject } from "hono/jsx"
|
||||
import { DateTime } from "luxon"
|
||||
|
||||
// URL Widget for clickable URLs
|
||||
class URLWidget extends WidgetType {
|
||||
constructor(readonly url: string, readonly text: string) {
|
||||
super()
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
const span = document.createElement("span")
|
||||
span.className = "todo-url"
|
||||
span.textContent = this.text
|
||||
span.addEventListener("click", (e) => {
|
||||
e.preventDefault()
|
||||
window.open(this.url, "_blank")
|
||||
})
|
||||
return span
|
||||
}
|
||||
}
|
||||
|
||||
// Effect to trigger a decoration refresh on filter change
|
||||
export const refreshFilterEffect = StateEffect.define<void>()
|
||||
|
|
@ -108,13 +89,12 @@ export const todoDecorations = (filterRef: RefObject<string>) => {
|
|||
builder.add(line.from, line.from + checkboxEnd, Decoration.mark({ class: "todo-checkbox" }))
|
||||
}
|
||||
|
||||
for (const { type, start, end, widget } of decorationsFor(todo)) {
|
||||
if (widget) {
|
||||
// Replace the text with a clickable widget
|
||||
builder.add(line.from + start, line.from + end, Decoration.replace({ widget }))
|
||||
} else {
|
||||
builder.add(line.from + start, line.from + end, Decoration.mark({ class: `todo-${type}` }))
|
||||
}
|
||||
for (const { type, start, end, attributes } of decorationsFor(todo)) {
|
||||
builder.add(
|
||||
line.from + start,
|
||||
line.from + end,
|
||||
Decoration.mark({ class: `todo-${type}`, attributes })
|
||||
)
|
||||
}
|
||||
} else if (/^\s*#+\s/.test(text)) {
|
||||
builder.add(line.from, line.to, Decoration.mark({ class: "todo-header" }))
|
||||
|
|
@ -132,21 +112,20 @@ export const todoDecorations = (filterRef: RefObject<string>) => {
|
|||
)
|
||||
}
|
||||
|
||||
type TodoAttributes = Record<string, string>
|
||||
const decorationsFor = (todo: Todo) => {
|
||||
const decorations: { type: string; start: number; end: number; widget?: WidgetType }[] = []
|
||||
const decorations: { type: string; start: number; end: number; attributes: TodoAttributes }[] = []
|
||||
|
||||
let start = 0
|
||||
for (const node of todo.nodes) {
|
||||
const { type } = node
|
||||
const end = start + node.content.length
|
||||
|
||||
const attributes: TodoAttributes = {}
|
||||
if (type === "url") {
|
||||
// Create a widget for clickable URLs
|
||||
const urlWidget = new URLWidget(node.url, node.content)
|
||||
decorations.push({ type: "url", start, end, widget: urlWidget })
|
||||
} else {
|
||||
decorations.push({ type, start, end })
|
||||
attributes["data-url"] = node.url
|
||||
}
|
||||
|
||||
decorations.push({ type, start, end, attributes })
|
||||
start = end
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,33 +11,7 @@ import { dateAutocompletion } from "@/dateAutocompletion"
|
|||
import { todoTimer } from "@/todoTimer"
|
||||
import { DateTime } from "luxon"
|
||||
|
||||
import "./editor.css"
|
||||
|
||||
// Constants
|
||||
const COMPLETION_ANIMATION_DURATION = 1000
|
||||
const FILTER_PLACEHOLDER = "Filter by tag"
|
||||
|
||||
const getDefaultDocument = () => {
|
||||
const today = DateTime.local().toFormat("MM/dd/yyyy")
|
||||
const future = DateTime.local().plus({ days: 3 }).toFormat("MM/dd/yyyy")
|
||||
|
||||
return `
|
||||
# Today
|
||||
|
||||
- [ ] Sample task with a due date @${today}
|
||||
- [ ] You can use a #tag to filter todos
|
||||
- [ ] A sub task! Create nested todos by indenting with <tab>
|
||||
- [x] Complete a todo by pressing <opt+k>
|
||||
- [ ] You can also set a time estimate for todos (press the play button to start) 10m
|
||||
|
||||
# This week
|
||||
- [ ] Another todo with a due date @${future}
|
||||
|
||||
# Later
|
||||
- [ ] I use later as a junk drawer for todos I don't want to forget
|
||||
|
||||
`.trim()
|
||||
}
|
||||
import "./todo.css"
|
||||
|
||||
type TodoEditorProps = {
|
||||
defaultValue?: string
|
||||
|
|
@ -171,3 +145,27 @@ const KeyboardShortcuts = ({ onClose }: { onClose: () => void }) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FILTER_PLACEHOLDER = "Filter by tag"
|
||||
|
||||
const getDefaultDocument = () => {
|
||||
const today = DateTime.local().toFormat("MM/dd/yyyy")
|
||||
const future = DateTime.local().plus({ days: 3 }).toFormat("MM/dd/yyyy")
|
||||
|
||||
return `
|
||||
# Today
|
||||
|
||||
- [ ] Sample task with a due date @${today}
|
||||
- [ ] You can use a #tag to filter todos
|
||||
- [ ] A sub task! Create nested todos by indenting with <tab>
|
||||
- [x] Complete a todo by pressing <opt+k>
|
||||
- [ ] You can also set a time estimate for todos (press the play button to start) 10m
|
||||
|
||||
# This week
|
||||
- [ ] Another todo with a due date @${future}
|
||||
|
||||
# Later
|
||||
- [ ] I use later as a junk drawer for todos I don't want to forget
|
||||
|
||||
`.trim()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"module": "src/index.tsx",
|
||||
"scripts": {
|
||||
"dev": "bun --hot src/server.tsx",
|
||||
"serve-subdomain": "NODE_ENV=production bun src/server.tsx"
|
||||
"serve-subdomain": "bun src/server.tsx"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user