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:
Aslam
2024-10-18 21:18:20 +07:00
committed by GitHub
parent c93c634a77
commit a440828f8c
158 changed files with 2808 additions and 1064 deletions

View File

@@ -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

View 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)
}

View 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)) {}

View File

@@ -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"

View File

@@ -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)) {}

View File

@@ -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

View File

@@ -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)) {}

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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://",