fubi 1.0.0-beta.2 → 1.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.es.js +28032 -0
- package/dist/index.iife.js +5 -0
- package/package.json +33 -33
- package/.prettierignore +0 -9
- package/.prettierrc +0 -23
- package/.vscode/extensions.json +0 -3
- package/config.toml +0 -3
- package/demo/index.html +0 -64
- package/index.html +0 -19
- package/publish.js +0 -77
- package/src/components/App.svelte +0 -17
- package/src/components/Debug.svelte +0 -39
- package/src/components/Layout.svelte +0 -47
- package/src/components/comment/CommentCard.svelte +0 -49
- package/src/components/comment/CommentFocusElement.svelte +0 -14
- package/src/components/comment/CommentMenu.svelte +0 -18
- package/src/components/comment/CommentPriority.svelte +0 -31
- package/src/components/comment/CommentReplyButton.svelte +0 -35
- package/src/components/comment/CommentUser.svelte +0 -22
- package/src/components/icons/AlertIcon.svelte +0 -7
- package/src/components/icons/CancelIcon.svelte +0 -8
- package/src/components/icons/CheckCircle.svelte +0 -7
- package/src/components/icons/CheckIcon.svelte +0 -7
- package/src/components/icons/CheckSmallIcon.svelte +0 -7
- package/src/components/icons/ChevronLeftIcon.svelte +0 -7
- package/src/components/icons/ChevronRightIcon.svelte +0 -7
- package/src/components/icons/ChevronsUpIcon.svelte +0 -8
- package/src/components/icons/DotsIcon.svelte +0 -9
- package/src/components/icons/PlusIcon.svelte +0 -8
- package/src/components/index.ts +0 -0
- package/src/components/toolbar/ButtonToolbar.svelte +0 -29
- package/src/components/toolbar/Menu.svelte +0 -42
- package/src/components/toolbar/Toolbar.svelte +0 -55
- package/src/components/ui/Button.svelte +0 -33
- package/src/components/ui/ButtonIcon.svelte +0 -31
- package/src/components/ui/ButtonSmall.svelte +0 -12
- package/src/components/ui/ButtonTabbar.svelte +0 -35
- package/src/components/ui/Chip.svelte +0 -28
- package/src/components/ui/Icon.svelte +0 -38
- package/src/components/ui/Input.svelte +0 -9
- package/src/components/ui/List.svelte +0 -18
- package/src/components/ui/ListItem.svelte +0 -87
- package/src/components/ui/ListLoader.svelte +0 -20
- package/src/components/ui/Loader.svelte +0 -39
- package/src/components/ui/Logo.svelte +0 -20
- package/src/components/ui/Msg.svelte +0 -21
- package/src/components/ui/Navbar.svelte +0 -33
- package/src/components/ui/Page.svelte +0 -67
- package/src/components/ui/Tabbar.svelte +0 -60
- package/src/components/ui/Toggle.svelte +0 -8
- package/src/components/ui/Window.svelte +0 -61
- package/src/index.ts +0 -28
- package/src/lib/actions.ts +0 -58
- package/src/lib/haptics.ts +0 -94
- package/src/lib/logger.ts +0 -22
- package/src/lib/routes.ts +0 -37
- package/src/lib/utils.ts +0 -27
- package/src/modules/app.svelte.ts +0 -66
- package/src/modules/auth.svelte.ts +0 -80
- package/src/modules/collection.svelte.ts +0 -79
- package/src/modules/comments.svelte.ts +0 -34
- package/src/modules/dom.svelte.ts +0 -26
- package/src/modules/domains.svelte.ts +0 -91
- package/src/modules/environment.svelte.ts +0 -10
- package/src/modules/events.svelte.ts +0 -23
- package/src/modules/folders.svelte.ts +0 -64
- package/src/modules/home.svelte.ts +0 -29
- package/src/modules/hover.svelte.ts +0 -3
- package/src/modules/index.ts +0 -28
- package/src/modules/keys.svelte.ts +0 -72
- package/src/modules/module.svelte.ts +0 -47
- package/src/modules/navbar.svelte.ts +0 -11
- package/src/modules/pages.svelte.ts +0 -126
- package/src/modules/project.svelte.ts +0 -70
- package/src/modules/router.svelte.ts +0 -99
- package/src/modules/tabbar.svelte.ts +0 -21
- package/src/modules/teams.svelte.ts +0 -0
- package/src/modules/toolbar.svelte.ts +0 -34
- package/src/modules/watch.svelte.ts +0 -8
- package/src/modules/win.svelte.ts +0 -273
- package/src/pages/Comments.svelte +0 -32
- package/src/pages/Error.svelte +0 -25
- package/src/pages/Folders.svelte +0 -53
- package/src/pages/Login.svelte +0 -44
- package/src/pages/Pages.svelte +0 -75
- package/src/pages/Thread.svelte +0 -11
- package/src/styles/global.css +0 -16
- package/src/styles/router.css +0 -79
- package/src/styles/styles.css +0 -159
- package/src/styles/tailwind.css +0 -115
- package/src/styles/variables.css +0 -99
- package/src/test.ts +0 -22
- package/src/types/fubi.ts +0 -0
- package/src/types/msg.ts +0 -5
- package/src/types/pocketbase.ts +0 -7
- package/src/types/user.ts +0 -8
- package/svelte.config.js +0 -11
- package/tsconfig.json +0 -41
- package/vite.config.ts +0 -40
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { ScrollState } from "runed";
|
|
3
|
-
import { getModules } from "@modules";
|
|
4
|
-
|
|
5
|
-
const { tabbar, navbar, win, router } = getModules();
|
|
6
|
-
|
|
7
|
-
let { title = null, navbar: navbarVisible = true, tabbar: tabbarVisible = true, name = "", children } = $props();
|
|
8
|
-
|
|
9
|
-
let current = $derived(router.current === name);
|
|
10
|
-
let previous = $derived(router.previous === name);
|
|
11
|
-
let next = $derived(router.next === name);
|
|
12
|
-
|
|
13
|
-
let active = $derived(router.navigating
|
|
14
|
-
? router.direction === "forward"
|
|
15
|
-
? router.next === name
|
|
16
|
-
: router.previous === name
|
|
17
|
-
: router.current === name);
|
|
18
|
-
|
|
19
|
-
let scrollContentEl = $state<HTMLElement>();
|
|
20
|
-
|
|
21
|
-
const scrollContentState = new ScrollState({
|
|
22
|
-
element: () => scrollContentEl,
|
|
23
|
-
onScroll: (e) => {
|
|
24
|
-
win.contentScroll = scrollContentState.y;
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
$effect(() => {
|
|
29
|
-
if (active) {
|
|
30
|
-
navbar.title = title;
|
|
31
|
-
navbar.visible = navbarVisible;
|
|
32
|
-
tabbar.visible = tabbarVisible;
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
</script>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<div
|
|
39
|
-
data-fubi-page={name}
|
|
40
|
-
inert={!current}
|
|
41
|
-
class={["page rim touch:rim-top flex min-w-full w-full flex-col rounded-(--fubi-rounded) mouse:rounded-(--fubi-rounded-mouse) touch:rounded-b-none h-full",
|
|
42
|
-
"from-(--fubi-page-from-bg) to-(--fubi-page-to-bg)",
|
|
43
|
-
"mouse:bg-gradient-to-b touch:bg-gradient-to-b from-(--fubi-page-from-bg) to-(--fubi-page-to-bg)",
|
|
44
|
-
"transition-[translate,scale,opacity] ease-out-cubic duration-500 top-0",
|
|
45
|
-
current && "page-current",
|
|
46
|
-
previous && "page-previous",
|
|
47
|
-
next && "page-next"
|
|
48
|
-
]}
|
|
49
|
-
>
|
|
50
|
-
<div
|
|
51
|
-
tabindex="-1"
|
|
52
|
-
class={[
|
|
53
|
-
"page-content flex flex-col hide-scrollbar",
|
|
54
|
-
"focus-visible:outline-none",
|
|
55
|
-
win.dragging.active && "pointer-events-none select-none",
|
|
56
|
-
"z-[8] relative overflow-auto h-full rounded-(--fubi-rounded) mouse:rounded-(--fubi-rounded-mouse)",
|
|
57
|
-
"transition-[padding-top] duration-300 ease-out will-change-[padding-top]",
|
|
58
|
-
navbarVisible && `pt-(--fubi-navbar-height) touch:pt-(--fubi-navbar-height-touch)`,
|
|
59
|
-
tabbarVisible && `pb-(--fubi-tabbar-height) touch:pb-(--fubi-tabbar-height-touch)`,
|
|
60
|
-
`px-12 touch:px-16`,
|
|
61
|
-
]}
|
|
62
|
-
bind:this={scrollContentEl}
|
|
63
|
-
>
|
|
64
|
-
{@render children()}
|
|
65
|
-
<!-- <div class="h-62 touch:h-76 w-full mouse:rounded-(--fubi-rounded-mouse) from-neutral-900/0 to-neutral-900 bg-gradient-to-b shrink-0 sticky bottom-0 left-0"></div> -->
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { getModules } from "@modules";
|
|
3
|
-
|
|
4
|
-
import { HugeiconsIcon } from "@hugeicons/svelte";
|
|
5
|
-
import ButtonIcon from "./ButtonIcon.svelte";
|
|
6
|
-
import ButtonTabbar from "@ui/ButtonTabbar.svelte";
|
|
7
|
-
|
|
8
|
-
// icons
|
|
9
|
-
import { Folder01Icon, FileEmpty02Icon, Comment03Icon } from "@hugeicons-pro/core-solid-rounded";
|
|
10
|
-
|
|
11
|
-
const { tabbar, folders, router } = getModules();
|
|
12
|
-
|
|
13
|
-
let tabs = $derived([
|
|
14
|
-
{ path: "folders", icon: Folder01Icon, alt: "Folders" },
|
|
15
|
-
{ path: "pages", icon: FileEmpty02Icon, alt: "Pages" },
|
|
16
|
-
{ path: "comments", icon: Comment03Icon, alt: "Comments" },
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
let tabActive = $derived(
|
|
20
|
-
(router.navigating ? (router.direction === "forward" ? router.next : router.previous) : router.current) === "comments"
|
|
21
|
-
? "comments"
|
|
22
|
-
: (router.navigating ? (router.direction === "forward" ? router.next : router.previous) : router.current) === "folders"
|
|
23
|
-
? "folders"
|
|
24
|
-
: "pages",
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
let backLabel = $derived(tabActive === "comments" ? "pages" : tabActive === "folders" ? "folders" : "folders")
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<div
|
|
31
|
-
class={[
|
|
32
|
-
"absolute w-full bottom-0 left-0 right-0 py-14 touch:py-16 mouse:justify-center touch:justify-center flex items-start gap-4 z-10","transition-[translate,opacity]",
|
|
33
|
-
tabbar.visible ? "ease-out-cubic duration-400" : "translate-y-full pointer-events-none opacity-0 ease-in duration-200"
|
|
34
|
-
]}
|
|
35
|
-
>
|
|
36
|
-
<!-- <div class="touch:hidden">
|
|
37
|
-
<ButtonIcon icon="chevron-left" alt="Back" label={backLabel} onclick={() => router.goto(backLabel, "back")} />
|
|
38
|
-
</div> -->
|
|
39
|
-
|
|
40
|
-
<div class="toolbar overflow-clip flex bg-neutral-500/10 glass backdrop-blur rounded-full p-4 relative z-10 pointer-events-auto">
|
|
41
|
-
<div
|
|
42
|
-
class={[
|
|
43
|
-
"absolute right-4 top-1/2 -translate-y-1/2 flex h-32 w-36 touch:h-48 touch:w-56 rounded-full bg-slate-500/20 pointer-events-none",
|
|
44
|
-
"transition-[translate,scale] duration-300 will-change-[transform]",
|
|
45
|
-
tabActive === "pages" && "-translate-x-full",
|
|
46
|
-
tabActive === "folders" && "-translate-x-[200%]",
|
|
47
|
-
router.navigating ? "scale-140 glass ease-out" : "ease-out"
|
|
48
|
-
]}
|
|
49
|
-
></div>
|
|
50
|
-
|
|
51
|
-
{#each tabs as { path, icon, alt }}
|
|
52
|
-
{#if path === "folders" && folders.unlockedCount > 1 || path !== "folders"}
|
|
53
|
-
<ButtonTabbar {path} {icon} {alt} active={tabActive === path} />
|
|
54
|
-
{/if}
|
|
55
|
-
{/each}
|
|
56
|
-
</div>
|
|
57
|
-
<!-- <div class="touch:hidden">
|
|
58
|
-
<ButtonIcon icon="dots" alt="Menu" />
|
|
59
|
-
</div> -->
|
|
60
|
-
</div>
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
let { name, checked = $bindable() } = $props();
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<label for={`fubi-checkbox-${name}`} class="block h-28 w-50 rounded-full bg-slate-500/20 hover:bg-slate-500/40 group/checkbox has-checked:bg-sky-500 relative transition-[background-color] ease-in-out-cubic duration-100 will-change-[background-color] focus-visible">
|
|
6
|
-
<input name={`fubi-checkbox-${name}`} id={`fubi-checkbox-${name}`} type="checkbox" class="absolute inset-0 opacity-0" bind:checked switch />
|
|
7
|
-
<div class="inset-4 rounded-full block absolute right-auto w-28 bg-white/80 shadow group-has-checked/checkbox:bg-white group-active/checkbox:w-32 transition-[width,background-color,translate] ease-out-cubic duration-100 group-has-checked/checkbox:translate-x-14 group-has-checked/checkbox:group-active/checkbox:translate-x-10 will-change-[width,background-color,translate]"></div>
|
|
8
|
-
</label>
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { getModules } from "@modules";
|
|
3
|
-
|
|
4
|
-
import Tabbar from "@components/ui/Tabbar.svelte";
|
|
5
|
-
import Navbar from "@ui/Navbar.svelte";
|
|
6
|
-
|
|
7
|
-
const { win } = getModules();
|
|
8
|
-
|
|
9
|
-
let { children = null } = $props();
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
|
-
<button
|
|
13
|
-
class={[
|
|
14
|
-
"fixed block inset-0 bg-transparent z-[1000] mouse:hidden",
|
|
15
|
-
win.opened ? "touch:pointer-events-auto" : "pointer-events-none"
|
|
16
|
-
]}
|
|
17
|
-
onclick={() => win.close()}>
|
|
18
|
-
<span class="sr-only">Close Window</span>
|
|
19
|
-
</button>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<div
|
|
23
|
-
bind:this={win.el}
|
|
24
|
-
class={[
|
|
25
|
-
// default styles
|
|
26
|
-
"window fixed left-0 right-0 touch:overflow-clip touch:rounded-b-none z-[1001] text-white -bottom-0.5",
|
|
27
|
-
"touch:rounded-t-(--fubi-rounded) touch:bg-neutral-900",
|
|
28
|
-
// breakpoint styles
|
|
29
|
-
"md:max-w-375",
|
|
30
|
-
// styles for desktop
|
|
31
|
-
"mouse:right-(--fubi-window-right) mouse:left-auto mouse:top-auto mouse:origin-bottom-right will-change-[translate,max-height,scale,opacity] mouse:bottom-(--fubi-window-bottom) mouse:w-300",
|
|
32
|
-
"transition-[max-height,opacity,scale] will-change-[max-height,opacity,scale]",
|
|
33
|
-
"mouse:max-h-1/2! mouse:min-h-[350px] h-[99dvh] mouse:h-500",
|
|
34
|
-
// when dragging the window
|
|
35
|
-
win.dragging.active
|
|
36
|
-
? "select-none duration-0"
|
|
37
|
-
: win.opened
|
|
38
|
-
? "duration-300 mouse:duration-200 ease-out"
|
|
39
|
-
: "duration-100 mouse:duration-200 ease",
|
|
40
|
-
|
|
41
|
-
// when window opened or closed
|
|
42
|
-
win.opened
|
|
43
|
-
? ""
|
|
44
|
-
: "mouse:opacity-0 mouse:scale-90 pointer-events-none",
|
|
45
|
-
]}
|
|
46
|
-
style={`max-height: ${win.dragging.active ? win.dragging.height : win.opened ? win.currentBreakpoint : 0}px;`}
|
|
47
|
-
onpointerdown={(e) => win.drag.start(e)}
|
|
48
|
-
onpointerup={(e) => win.drag.end(e)}
|
|
49
|
-
onpointermove={(e) => win.drag.move(e)}
|
|
50
|
-
onpointercancel={(e) => win.drag.end(e)}
|
|
51
|
-
>
|
|
52
|
-
<div class={["pointer-events-none rounded-full absolute left-1/2 -translate-x-1/2 z-[1001] mouse:hidden",
|
|
53
|
-
"transition-[background-color,box-shadow,scale,width,height,top] duration-100 ease-out will-change-[background-color,box-shadow,scale,width,height,top]",
|
|
54
|
-
"bg-slate-500/30 w-50 h-3 top-7"
|
|
55
|
-
]}></div>
|
|
56
|
-
<Navbar />
|
|
57
|
-
{#if children}
|
|
58
|
-
{@render children()}
|
|
59
|
-
{/if}
|
|
60
|
-
<Tabbar />
|
|
61
|
-
</div>
|
package/src/index.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { mount } from "svelte";
|
|
2
|
-
|
|
3
|
-
import App from "./components/App.svelte";
|
|
4
|
-
|
|
5
|
-
import styles from "./styles/styles.css?inline";
|
|
6
|
-
import stylesGlobal from "./styles/global.css?inline";
|
|
7
|
-
|
|
8
|
-
export const fubi = (projectId: string) => {
|
|
9
|
-
// check if projectId provided if not throw error
|
|
10
|
-
if (!projectId) {
|
|
11
|
-
throw new Error("Please provide a projectId");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const root = document.createElement("div");
|
|
15
|
-
root.id = "fubi-shadow-root";
|
|
16
|
-
|
|
17
|
-
const shadowRoot = root.attachShadow({ mode: "open" });
|
|
18
|
-
shadowRoot.innerHTML = `<style>${styles}</style>`;
|
|
19
|
-
|
|
20
|
-
const wrapper = document.createElement("div");
|
|
21
|
-
wrapper.id = "fubi-wrapper";
|
|
22
|
-
shadowRoot.appendChild(wrapper);
|
|
23
|
-
|
|
24
|
-
document.body.appendChild(root);
|
|
25
|
-
document.head.insertAdjacentHTML("beforeend", `<style id="fubi-global-styles">${stylesGlobal}</style>`);
|
|
26
|
-
|
|
27
|
-
mount(App, { target: wrapper, props: { projectId, wrapper } });
|
|
28
|
-
};
|
package/src/lib/actions.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { Action } from "svelte/action";
|
|
2
|
-
import { animate, AnimationPlaybackControls, press as pressMotion } from "motion";
|
|
3
|
-
|
|
4
|
-
type AnimateParams = Parameters<typeof animate>[1];
|
|
5
|
-
|
|
6
|
-
interface PressActionParams {
|
|
7
|
-
animate: AnimateParams;
|
|
8
|
-
reset: AnimateParams;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface SelectedActionParams {
|
|
12
|
-
active: boolean;
|
|
13
|
-
animate: AnimateParams;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const press: Action<HTMLElement, PressActionParams> = (node, params) => {
|
|
17
|
-
pressMotion(node, (pressedNode) => {
|
|
18
|
-
animate(pressedNode, params.animate, { type: "spring", duration: 0.2 });
|
|
19
|
-
|
|
20
|
-
return () => animate(pressedNode, params.reset);
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const selected: Action<HTMLElement, SelectedActionParams> = (node, params) => {
|
|
25
|
-
let animation: AnimationPlaybackControls;
|
|
26
|
-
|
|
27
|
-
const updateAnimation = (isActive: boolean) => {
|
|
28
|
-
// Cancel any existing animation first
|
|
29
|
-
if (animation) animation.cancel();
|
|
30
|
-
|
|
31
|
-
if (isActive) {
|
|
32
|
-
// Animate TO the selected state
|
|
33
|
-
animation = animate(node, params.animate, { duration: 0.3 });
|
|
34
|
-
} else {
|
|
35
|
-
// Animate BACK to the original state
|
|
36
|
-
animation = animate(
|
|
37
|
-
node,
|
|
38
|
-
{
|
|
39
|
-
scale: 1,
|
|
40
|
-
borderRadius: 0,
|
|
41
|
-
backgroundColor: "transparent",
|
|
42
|
-
},
|
|
43
|
-
{ duration: 0.3 },
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
updateAnimation(params.active);
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
update(newParams) {
|
|
52
|
-
updateAnimation(newParams.active);
|
|
53
|
-
},
|
|
54
|
-
destroy() {
|
|
55
|
-
if (animation) animation.cancel();
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
};
|
package/src/lib/haptics.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export const supportsHaptics = typeof window === "undefined" ? false : window.matchMedia("(pointer: coarse)").matches;
|
|
2
|
-
|
|
3
|
-
function _haptic() {
|
|
4
|
-
try {
|
|
5
|
-
if (navigator.vibrate) {
|
|
6
|
-
navigator.vibrate(50);
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (!supportsHaptics) return;
|
|
11
|
-
|
|
12
|
-
const labelEl = document.createElement("label");
|
|
13
|
-
labelEl.ariaHidden = "true";
|
|
14
|
-
labelEl.style.display = "none";
|
|
15
|
-
|
|
16
|
-
const inputEl = document.createElement("input");
|
|
17
|
-
inputEl.type = "checkbox";
|
|
18
|
-
inputEl.setAttribute("switch", "");
|
|
19
|
-
labelEl.appendChild(inputEl);
|
|
20
|
-
|
|
21
|
-
document.head.appendChild(labelEl);
|
|
22
|
-
labelEl.click();
|
|
23
|
-
document.head.removeChild(labelEl);
|
|
24
|
-
} catch {
|
|
25
|
-
// do nothing
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
_haptic.confirm = () => {
|
|
30
|
-
if (navigator.vibrate) {
|
|
31
|
-
navigator.vibrate([50, 70, 50]);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
_haptic();
|
|
36
|
-
setTimeout(() => _haptic(), 120);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
_haptic.error = () => {
|
|
40
|
-
if (navigator.vibrate) {
|
|
41
|
-
navigator.vibrate([50, 70, 50, 70, 50]);
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
_haptic();
|
|
46
|
-
setTimeout(() => _haptic(), 120);
|
|
47
|
-
setTimeout(() => _haptic(), 240);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// prevent intellisense from being unhelpful
|
|
51
|
-
interface haptic {
|
|
52
|
-
/** @deprecated */
|
|
53
|
-
apply: never;
|
|
54
|
-
|
|
55
|
-
/** @deprecated */
|
|
56
|
-
arguments: never;
|
|
57
|
-
|
|
58
|
-
/** @deprecated */
|
|
59
|
-
bind: never;
|
|
60
|
-
|
|
61
|
-
/** @deprecated */
|
|
62
|
-
call: never;
|
|
63
|
-
|
|
64
|
-
/** @deprecated */
|
|
65
|
-
caller: never;
|
|
66
|
-
|
|
67
|
-
/** @deprecated */
|
|
68
|
-
length: never;
|
|
69
|
-
|
|
70
|
-
/** @deprecated */
|
|
71
|
-
name: never;
|
|
72
|
-
|
|
73
|
-
/** @deprecated */
|
|
74
|
-
prototype: never;
|
|
75
|
-
|
|
76
|
-
/** @deprecated */
|
|
77
|
-
toString: never;
|
|
78
|
-
|
|
79
|
-
/** @deprecated */
|
|
80
|
-
Symbol: never;
|
|
81
|
-
|
|
82
|
-
/** a single haptic */
|
|
83
|
-
(): void;
|
|
84
|
-
|
|
85
|
-
/** two rapid haptics */
|
|
86
|
-
confirm: () => void;
|
|
87
|
-
|
|
88
|
-
/** three rapid haptics */
|
|
89
|
-
error: () => void;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const __haptic = _haptic as haptic;
|
|
93
|
-
|
|
94
|
-
export { __haptic as haptics };
|
package/src/lib/logger.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
const debug = true;
|
|
2
|
-
|
|
3
|
-
export function logger(module: string) {
|
|
4
|
-
return {
|
|
5
|
-
info: (...params: any[]) => {
|
|
6
|
-
if (debug) console.log(`%c➜ [${module}]`, "color: #38bdf8", ...params);
|
|
7
|
-
},
|
|
8
|
-
error: (...params: any[]) => {
|
|
9
|
-
if (debug) console.error(`[${module}]`, ...params);
|
|
10
|
-
},
|
|
11
|
-
warn: (...params: any[]) => {
|
|
12
|
-
if (debug) console.warn(`[${module}]`, ...params);
|
|
13
|
-
},
|
|
14
|
-
ok: (...params: any[]) => {
|
|
15
|
-
if (debug) console.log(`%c✔ [${module}]`, "color: #22c55e", ...params);
|
|
16
|
-
},
|
|
17
|
-
group: (label?: string) => {
|
|
18
|
-
label ? console.group(`%c—— ${label} ——`, "color: #6366f1") : console.group();
|
|
19
|
-
},
|
|
20
|
-
groupEnd: () => console.groupEnd(),
|
|
21
|
-
};
|
|
22
|
-
}
|
package/src/lib/routes.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Component } from "svelte";
|
|
2
|
-
|
|
3
|
-
import Login from "@pages/Login.svelte";
|
|
4
|
-
import Folders from "@pages/Folders.svelte";
|
|
5
|
-
import Pages from "@pages/Pages.svelte";
|
|
6
|
-
import Comments from "@pages/Comments.svelte";
|
|
7
|
-
import Thread from "@pages/Thread.svelte";
|
|
8
|
-
import Error from "@pages/Error.svelte";
|
|
9
|
-
|
|
10
|
-
export type RouteType = {
|
|
11
|
-
component: Component;
|
|
12
|
-
default?: boolean;
|
|
13
|
-
direction?: "forward" | "back";
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export const routes: Record<string, RouteType> = {
|
|
17
|
-
login: {
|
|
18
|
-
component: Login,
|
|
19
|
-
},
|
|
20
|
-
folders: {
|
|
21
|
-
component: Folders,
|
|
22
|
-
},
|
|
23
|
-
pages: {
|
|
24
|
-
component: Pages,
|
|
25
|
-
},
|
|
26
|
-
comments: {
|
|
27
|
-
component: Comments,
|
|
28
|
-
},
|
|
29
|
-
thread: {
|
|
30
|
-
component: Thread,
|
|
31
|
-
},
|
|
32
|
-
error: {
|
|
33
|
-
component: Error,
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type Routes = keyof typeof routes;
|
package/src/lib/utils.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { pocketbaseUrl } from "@modules/app.svelte";
|
|
2
|
-
import { CollectionItem } from "@/types/pocketbase.js";
|
|
3
|
-
|
|
4
|
-
export function hasFocus(): boolean {
|
|
5
|
-
let activeEl = document.activeElement as HTMLElement | null;
|
|
6
|
-
if (!activeEl) return false;
|
|
7
|
-
|
|
8
|
-
while (activeEl?.shadowRoot?.activeElement) {
|
|
9
|
-
activeEl = activeEl.shadowRoot.activeElement as HTMLElement;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (activeEl.tagName === "INPUT" || activeEl.tagName === "TEXTAREA") return true;
|
|
13
|
-
|
|
14
|
-
if (activeEl.contentEditable === "true") return true;
|
|
15
|
-
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function getAssetUrl(record: CollectionItem, field: string): string | null {
|
|
20
|
-
if (!record) return null;
|
|
21
|
-
|
|
22
|
-
if (!record[field]) return null;
|
|
23
|
-
|
|
24
|
-
const { id, collectionName } = record;
|
|
25
|
-
|
|
26
|
-
return `${pocketbaseUrl}/api/files/${collectionName}/${id}/${record[field]}`;
|
|
27
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import PocketBase from "pocketbase";
|
|
2
|
-
import {
|
|
3
|
-
Win,
|
|
4
|
-
Navbar,
|
|
5
|
-
Tabbar,
|
|
6
|
-
Router,
|
|
7
|
-
Auth,
|
|
8
|
-
Project,
|
|
9
|
-
Folders,
|
|
10
|
-
Pages,
|
|
11
|
-
Comments,
|
|
12
|
-
Events,
|
|
13
|
-
Toolbar,
|
|
14
|
-
Keys,
|
|
15
|
-
Dom,
|
|
16
|
-
Environment,
|
|
17
|
-
Hover,
|
|
18
|
-
Domains,
|
|
19
|
-
Home,
|
|
20
|
-
} from ".";
|
|
21
|
-
|
|
22
|
-
export const pocketbaseUrl = "https://fubi.wondermakers.dev";
|
|
23
|
-
|
|
24
|
-
import { logger } from "@logger";
|
|
25
|
-
const log = logger("app");
|
|
26
|
-
|
|
27
|
-
export const createApp = (params: { projectId: string; wrapper: HTMLElement }) => {
|
|
28
|
-
const { projectId, wrapper } = params;
|
|
29
|
-
|
|
30
|
-
const modules = {
|
|
31
|
-
win: new Win(),
|
|
32
|
-
navbar: new Navbar(),
|
|
33
|
-
tabbar: new Tabbar(),
|
|
34
|
-
router: new Router(),
|
|
35
|
-
auth: new Auth(),
|
|
36
|
-
pb: new PocketBase(pocketbaseUrl),
|
|
37
|
-
project: new Project(projectId),
|
|
38
|
-
folders: new Folders(),
|
|
39
|
-
pages: new Pages(),
|
|
40
|
-
events: new Events(),
|
|
41
|
-
comments: new Comments(),
|
|
42
|
-
domains: new Domains(),
|
|
43
|
-
toolbar: new Toolbar(wrapper),
|
|
44
|
-
keys: new Keys(),
|
|
45
|
-
dom: new Dom(),
|
|
46
|
-
environment: new Environment(),
|
|
47
|
-
hover: new Hover(),
|
|
48
|
-
home: new Home(),
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// init modules first
|
|
52
|
-
Object.values(modules).forEach((m: any) => {
|
|
53
|
-
if (m.init) m.init?.(modules);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
log.ok(`All modules initialized.`);
|
|
57
|
-
|
|
58
|
-
// start modules
|
|
59
|
-
Object.values(modules).forEach((m: any) => {
|
|
60
|
-
if (m.start) m.start?.();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
return { modules };
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export type App = ReturnType<typeof createApp>;
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { MsgType } from "./../types/msg";
|
|
2
|
-
import { Module } from "./module.svelte";
|
|
3
|
-
|
|
4
|
-
import { logger } from "@logger";
|
|
5
|
-
const log = logger("auth");
|
|
6
|
-
|
|
7
|
-
import type { UserType } from "@/types/user";
|
|
8
|
-
export class Auth extends Module {
|
|
9
|
-
loading = $state(false);
|
|
10
|
-
user = $state(null) as UserType | null;
|
|
11
|
-
isAllowed = $derived(this.modules.project.isTeamMember || this.modules.folders.unlockedCount > 0);
|
|
12
|
-
|
|
13
|
-
msg: MsgType = $state({
|
|
14
|
-
type: "info",
|
|
15
|
-
text: "",
|
|
16
|
-
visible: false,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
start() {
|
|
20
|
-
this.user = this.modules.pb.authStore.record as UserType | null;
|
|
21
|
-
|
|
22
|
-
this.modules.pb.authStore.onChange(async () => {
|
|
23
|
-
this.user = this.modules.pb.authStore.record as UserType | null;
|
|
24
|
-
await this.checkAccess();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
this.checkAccess();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Checks if user is allowed (team member/admin or has folder access) and routes accordingly
|
|
32
|
-
*/
|
|
33
|
-
private async checkAccess() {
|
|
34
|
-
if (this.user) {
|
|
35
|
-
await this.modules.project.checkTeamMembership(this.user.id); // check if user is team member or admin
|
|
36
|
-
await this.modules.folders.fetch(); // refetch the folders to update access
|
|
37
|
-
if (this.isAllowed) await this.modules.domains.verify(); // verify domain by allowed user
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// force logout user if not allowed
|
|
41
|
-
if (!this.isAllowed && this.user) {
|
|
42
|
-
this.msg = {
|
|
43
|
-
type: "error",
|
|
44
|
-
text: "You do not have access to this project or any of its folders.",
|
|
45
|
-
visible: true,
|
|
46
|
-
};
|
|
47
|
-
this.track("warn", "User does not have access to project or any folders", { user: this.user.id });
|
|
48
|
-
this.logout();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// navigate user based on access
|
|
52
|
-
const nextRoute: Array<string> = this.modules.router.determineRoute(this.user, this.isAllowed);
|
|
53
|
-
this.modules.router.goto(...(nextRoute as ["string", "forward" | "back"]));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async login(email: string, password: string) {
|
|
57
|
-
this.loading = true;
|
|
58
|
-
this.msg = { type: "info", text: "", visible: false };
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
await this.modules.pb.collection("users").authWithPassword(email, password);
|
|
62
|
-
this.user = this.modules.pb.authStore.record as UserType;
|
|
63
|
-
this.track("ok", "User logged in");
|
|
64
|
-
this.loading = false;
|
|
65
|
-
return { success: true };
|
|
66
|
-
} catch (error: any) {
|
|
67
|
-
this.track("error", "Login failed", { error: error?.message || error, email });
|
|
68
|
-
this.msg = { type: "error", text: error?.message || "Login failed", visible: true };
|
|
69
|
-
this.loading = false;
|
|
70
|
-
return { success: false, error: error?.message || error };
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async logout() {
|
|
75
|
-
if (!this.user) return;
|
|
76
|
-
|
|
77
|
-
this.modules.pb.authStore.clear();
|
|
78
|
-
this.user = null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { AuthRecord, RecordService, RecordSubscription } from "pocketbase";
|
|
2
|
-
import { CollectionItem } from "@/types/pocketbase";
|
|
3
|
-
import { Module } from "./module.svelte";
|
|
4
|
-
import { logger } from "@logger";
|
|
5
|
-
|
|
6
|
-
const log = logger("collection-module");
|
|
7
|
-
|
|
8
|
-
interface CollectionModuleOptions<T extends CollectionItem = CollectionItem> {
|
|
9
|
-
name: string;
|
|
10
|
-
filters?: string;
|
|
11
|
-
sort?: string;
|
|
12
|
-
expand?: string;
|
|
13
|
-
page?: number;
|
|
14
|
-
perPage?: number;
|
|
15
|
-
query?: Record<string, any>;
|
|
16
|
-
transform?: (record: T, authUser: AuthRecord | null) => T;
|
|
17
|
-
onChange?: (e: RecordSubscription) => void;
|
|
18
|
-
onFinish?: () => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export abstract class CollectionModule<T extends CollectionItem = CollectionItem> extends Module {
|
|
22
|
-
data = $state<T[]>([]);
|
|
23
|
-
loading = $state(false);
|
|
24
|
-
error = $state<string | null>(null);
|
|
25
|
-
filters = $state<string | undefined>(undefined);
|
|
26
|
-
sort = $state<string | undefined>(undefined);
|
|
27
|
-
page = $state<number>(1);
|
|
28
|
-
perPage = $state<number>(50);
|
|
29
|
-
|
|
30
|
-
empty = $derived(this.data.length === 0);
|
|
31
|
-
|
|
32
|
-
protected collection!: RecordService<T>;
|
|
33
|
-
private options!: CollectionModuleOptions<T>;
|
|
34
|
-
|
|
35
|
-
protected initCollection(options: CollectionModuleOptions<T>) {
|
|
36
|
-
this.options = options;
|
|
37
|
-
this.filters = options.filters;
|
|
38
|
-
this.sort = options.sort;
|
|
39
|
-
this.page = options.page || 1;
|
|
40
|
-
this.perPage = options.perPage || 50;
|
|
41
|
-
this.collection = this.modules.pb.collection(options.name);
|
|
42
|
-
this.collection.subscribe("*", (e) => {
|
|
43
|
-
this.fetch();
|
|
44
|
-
if (options.onChange) options.onChange(e);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async fetch() {
|
|
49
|
-
this.loading = true;
|
|
50
|
-
this.error = null;
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const result = await this.collection.getList<T>(this.page, this.perPage, {
|
|
54
|
-
filter: this.filters,
|
|
55
|
-
sort: this.sort,
|
|
56
|
-
expand: this.options.expand,
|
|
57
|
-
...this.options.query,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
log.info("fetched:");
|
|
61
|
-
console.log(result.items);
|
|
62
|
-
|
|
63
|
-
this.data = result.items.map((record) =>
|
|
64
|
-
this.options.transform ? this.options.transform(record, this.modules.pb.authStore.record) : record,
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
if (this.options.onFinish) this.options.onFinish();
|
|
68
|
-
} catch (err: any) {
|
|
69
|
-
this.error = err.message || "Failed to fetch";
|
|
70
|
-
log.error(`Error fetching ${this.options.name}:`, err);
|
|
71
|
-
} finally {
|
|
72
|
-
this.loading = false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
destroy() {
|
|
77
|
-
this.collection?.unsubscribe("*");
|
|
78
|
-
}
|
|
79
|
-
}
|