mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
chore: Enhancement + New Feature (#185)
* wip * wip page * chore: style * wip pages * wip pages * chore: toggle * chore: link * feat: topic search * chore: page section * refactor: apply tailwind class ordering * fix: handle loggedIn user for guest route * feat: folder & image schema * chore: move utils to shared * refactor: tailwind class ordering * feat: img ext for editor * refactor: remove qa * fix: tanstack start * fix: wrong import * chore: use toast * chore: schema
This commit is contained in:
@@ -28,13 +28,13 @@ export const LEARNING_STATES: LearningState[] = [
|
||||
label: "Learning",
|
||||
value: "learning",
|
||||
icon: "GraduationCap",
|
||||
className: "text-[#D29752]",
|
||||
className: "text-yellow-600 hover:text-yellow-700",
|
||||
},
|
||||
{
|
||||
label: "Learned",
|
||||
value: "learned",
|
||||
icon: "Check",
|
||||
className: "text-[#708F51]",
|
||||
className: "text-green-700 hover:text-green-800",
|
||||
},
|
||||
] as const
|
||||
|
||||
|
||||
6
web/app/lib/schema/base.ts
Normal file
6
web/app/lib/schema/base.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { co, CoMap, Encoders } from "jazz-tools"
|
||||
|
||||
export class BaseModel extends CoMap {
|
||||
createdAt = co.encoded(Encoders.Date)
|
||||
updatedAt = co.encoded(Encoders.Date)
|
||||
}
|
||||
26
web/app/lib/schema/folder.ts
Normal file
26
web/app/lib/schema/folder.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { co, CoList, ImageDefinition } from "jazz-tools"
|
||||
import { BaseModel } from "./base"
|
||||
|
||||
export class Image extends BaseModel {
|
||||
fileName = co.optional.string
|
||||
fileSize = co.optional.number
|
||||
width = co.optional.number
|
||||
height = co.optional.number
|
||||
aspectRatio = co.optional.number
|
||||
mimeType = co.optional.string
|
||||
lastModified = co.optional.string
|
||||
url = co.optional.string
|
||||
content = co.optional.ref(ImageDefinition)
|
||||
}
|
||||
|
||||
export class ImageLists extends CoList.Of(co.ref(Image)) {}
|
||||
|
||||
export class Folder extends BaseModel {
|
||||
name = co.string
|
||||
description = co.optional.string
|
||||
icon = co.optional.string
|
||||
images = co.optional.ref(ImageLists)
|
||||
parent = co.optional.ref(Folder)
|
||||
}
|
||||
|
||||
export class FolderLists extends CoList.Of(co.ref(Folder)) {}
|
||||
@@ -4,6 +4,7 @@ import { PersonalLinkLists } from "./personal-link"
|
||||
import { ListOfTopics } from "./master/topic"
|
||||
import { ListOfTasks } from "./task"
|
||||
import { JournalEntryLists } from "./journal"
|
||||
import { FolderLists, ImageLists } from "./folder"
|
||||
|
||||
declare module "jazz-tools" {
|
||||
interface Profile {
|
||||
@@ -12,6 +13,7 @@ declare module "jazz-tools" {
|
||||
}
|
||||
|
||||
export class UserRoot extends CoMap {
|
||||
version = co.optional.number
|
||||
name = co.string
|
||||
username = co.string
|
||||
avatar = co.optional.string
|
||||
@@ -28,6 +30,9 @@ export class UserRoot extends CoMap {
|
||||
|
||||
tasks = co.ref(ListOfTasks)
|
||||
journalEntries = co.ref(JournalEntryLists)
|
||||
|
||||
folders = co.ref(FolderLists)
|
||||
images = co.ref(ImageLists)
|
||||
}
|
||||
|
||||
export class LaAccount extends Account {
|
||||
@@ -38,9 +43,6 @@ export class LaAccount extends Account {
|
||||
this: LaAccount,
|
||||
creationProps?: { name: string; avatarUrl?: string },
|
||||
) {
|
||||
// since we dont have a custom AuthProvider yet.
|
||||
// and still using the DemoAuth. the creationProps will only accept name.
|
||||
// so just do default profile create provided by jazz-tools
|
||||
super.migrate(creationProps)
|
||||
|
||||
if (!this._refs.root && creationProps) {
|
||||
@@ -62,6 +64,11 @@ export class LaAccount extends Account {
|
||||
|
||||
tasks: ListOfTasks.create([], { owner: this }),
|
||||
journalEntries: JournalEntryLists.create([], { owner: this }),
|
||||
|
||||
folders: FolderLists.create([], { owner: this }),
|
||||
images: ImageLists.create([], { owner: this }),
|
||||
|
||||
version: 1,
|
||||
},
|
||||
{ owner: this },
|
||||
)
|
||||
@@ -72,3 +79,6 @@ export class LaAccount extends Account {
|
||||
export * from "./master/topic"
|
||||
export * from "./personal-link"
|
||||
export * from "./personal-page"
|
||||
export * from "./task"
|
||||
export * from "./journal"
|
||||
export * from "./folder"
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { co, CoList, CoMap, Encoders } from "jazz-tools"
|
||||
import { co, CoList, Encoders } from "jazz-tools"
|
||||
import { BaseModel } from "./base"
|
||||
|
||||
export class JournalEntry extends CoMap {
|
||||
export class JournalEntry extends BaseModel {
|
||||
title = co.string
|
||||
content = co.json()
|
||||
date = co.encoded(Encoders.Date)
|
||||
createdAt = co.encoded(Encoders.Date)
|
||||
updatedAt = co.encoded(Encoders.Date)
|
||||
}
|
||||
|
||||
export class JournalEntryLists extends CoList.Of(co.ref(JournalEntry)) {}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { co, CoList, CoMap, Encoders } from "jazz-tools"
|
||||
import { co, CoList } from "jazz-tools"
|
||||
import { Link, Topic } from "./master/topic"
|
||||
|
||||
class BaseModel extends CoMap {
|
||||
createdAt = co.encoded(Encoders.Date)
|
||||
updatedAt = co.encoded(Encoders.Date)
|
||||
}
|
||||
import { BaseModel } from "./base"
|
||||
|
||||
export class PersonalLink extends BaseModel {
|
||||
url = co.string
|
||||
icon = co.optional.string // is an icon URL
|
||||
icon = co.optional.string
|
||||
link = co.optional.ref(Link)
|
||||
title = co.string
|
||||
slug = co.string
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import { co, CoList, CoMap, Encoders } from "jazz-tools"
|
||||
import { co, CoList } from "jazz-tools"
|
||||
import { Topic } from "./master/topic"
|
||||
import { BaseModel } from "./base"
|
||||
|
||||
/*
|
||||
* Page, content that user can write to. Similar to Notion/Reflect page. It holds ProseMirror editor content + metadata.
|
||||
* - slug: make it unique
|
||||
* - Public Access, url should be learn-anything.xyz/@user/slug
|
||||
* - if public, certain members (can do read/write access accordingly), personal (end to end encrypted, only accessed by user)
|
||||
*/
|
||||
export class PersonalPage extends CoMap {
|
||||
export class PersonalPage extends BaseModel {
|
||||
title = co.optional.string
|
||||
slug = co.optional.string // is used only when `public: true` for sharing, `@user/page-slug`
|
||||
slug = co.optional.string
|
||||
public = co.boolean
|
||||
content = co.optional.json()
|
||||
topic = co.optional.ref(Topic)
|
||||
createdAt = co.encoded(Encoders.Date)
|
||||
updatedAt = co.encoded(Encoders.Date)
|
||||
// backlinks = co.optional.ref() // other PersonalPages linking to this page TODO: add, think through how to do it well, efficiently
|
||||
}
|
||||
|
||||
export class PersonalPageLists extends CoList.Of(co.ref(PersonalPage)) {}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { co, CoList, CoMap, Encoders } from "jazz-tools"
|
||||
import { co, CoList, Encoders } from "jazz-tools"
|
||||
import { BaseModel } from "./base"
|
||||
|
||||
export class Task extends CoMap {
|
||||
export class Task extends BaseModel {
|
||||
title = co.string
|
||||
description = co.optional.string
|
||||
status = co.literal("todo", "in_progress", "done")
|
||||
createdAt = co.encoded(Encoders.Date)
|
||||
updatedAt = co.encoded(Encoders.Date)
|
||||
dueDate = co.optional.encoded(Encoders.Date)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,6 @@ export function shuffleArray<T>(array: T[]): T[] {
|
||||
return shuffled
|
||||
}
|
||||
|
||||
export const isClient = () => typeof window !== "undefined"
|
||||
|
||||
export const isServer = () => !isClient()
|
||||
|
||||
const inputs = ["input", "select", "button", "textarea"] // detect if node is a text input element
|
||||
|
||||
export function isTextInput(element: Element): boolean {
|
||||
@@ -75,7 +71,6 @@ export function calendarFormatDate(date: Date): string {
|
||||
}
|
||||
|
||||
export * from "./force-graph"
|
||||
export * from "./keyboard"
|
||||
export * from "./env"
|
||||
export * from "./slug"
|
||||
export * from "./url"
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { isServer } from "."
|
||||
|
||||
interface ShortcutKeyResult {
|
||||
symbol: string
|
||||
readable: string
|
||||
}
|
||||
|
||||
export function getShortcutKey(key: string): ShortcutKeyResult {
|
||||
const lowercaseKey = key.toLowerCase()
|
||||
if (lowercaseKey === "mod") {
|
||||
return isMac()
|
||||
? { symbol: "⌘", readable: "Command" }
|
||||
: { symbol: "Ctrl", readable: "Control" }
|
||||
} else if (lowercaseKey === "alt") {
|
||||
return isMac()
|
||||
? { symbol: "⌥", readable: "Option" }
|
||||
: { symbol: "Alt", readable: "Alt" }
|
||||
} else if (lowercaseKey === "shift") {
|
||||
return isMac()
|
||||
? { symbol: "⇧", readable: "Shift" }
|
||||
: { symbol: "Shift", readable: "Shift" }
|
||||
} else {
|
||||
return { symbol: key.toUpperCase(), readable: key }
|
||||
}
|
||||
}
|
||||
|
||||
export function getShortcutKeys(keys: string[]): ShortcutKeyResult[] {
|
||||
return keys.map((key) => getShortcutKey(key))
|
||||
}
|
||||
|
||||
export function isModKey(
|
||||
event: KeyboardEvent | MouseEvent | React.KeyboardEvent,
|
||||
) {
|
||||
return isMac() ? event.metaKey : event.ctrlKey
|
||||
}
|
||||
|
||||
export function isMac(): boolean {
|
||||
if (isServer()) {
|
||||
return false
|
||||
}
|
||||
return window.navigator.platform === "MacIntel"
|
||||
}
|
||||
|
||||
export function isWindows(): boolean {
|
||||
if (isServer()) {
|
||||
return false
|
||||
}
|
||||
return window.navigator.platform === "Win32"
|
||||
}
|
||||
|
||||
let supportsPassive = false
|
||||
|
||||
try {
|
||||
const opts = Object.defineProperty({}, "passive", {
|
||||
get() {
|
||||
supportsPassive = true
|
||||
},
|
||||
})
|
||||
// @ts-expect-error ts-migrate(2769) testPassive is not a real event
|
||||
window.addEventListener("testPassive", null, opts)
|
||||
// @ts-expect-error ts-migrate(2769) testPassive is not a real event
|
||||
window.removeEventListener("testPassive", null, opts)
|
||||
} catch (e) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
export const supportsPassiveListener = supportsPassive
|
||||
@@ -1,18 +1,3 @@
|
||||
export function isValidUrl(string: string): boolean {
|
||||
try {
|
||||
new URL(string)
|
||||
return true
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isUrl(text: string): boolean {
|
||||
const pattern: RegExp =
|
||||
/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/
|
||||
return pattern.test(text)
|
||||
}
|
||||
|
||||
export function ensureUrlProtocol(
|
||||
url: string,
|
||||
defaultProtocol: string = "https://",
|
||||
|
||||
Reference in New Issue
Block a user