Skip to content

Commit

Permalink
Cloud sync with supbase (#1)
Browse files Browse the repository at this point in the history
* buggy adapter

* chore: update dependencies

* feat: basic input output

* fix: fallback when no supabase setting

* fix: types

* feat: functional import export

* fix: loading optimize

* fix: tweak debounce timeout

* feat: login logout button

* fix: title of created page

* fix: delete block issue

* refactor: optimize delete block ui

* chore: Docker build

* chore: github workflow

* fix: favicon

* chore: static Docker

* feat: directly return 404

* chore: enable docker image push on master branch

* feat: github login and init sql

* chore: build static in workflow
  • Loading branch information
fengkx authored Aug 20, 2021
1 parent 66a2cb2 commit 6cfb8a7
Show file tree
Hide file tree
Showing 39 changed files with 7,694 additions and 5,427 deletions.
25 changes: 25 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Docker
Dockerfile

# dependencies
/node_modules
/.pnp
.pnp.js

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

# vercel
.vercel
61 changes: 61 additions & 0 deletions .github/workflows/dockerimage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Publish Docker
on:
- push
- pull_request_target

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Cache pnpm modules
uses: actions/cache@v2
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-
- name: Set up pnpm
uses: pnpm/[email protected]
with:
version: 6
run_install: true
- name: Build static
shell: bash
id: build-static
run: |
export NEXT_PUBLIC_SUPABASE_URL=ECALPER_EB_OT_GNIRTS_EUQINU_YREV_EMOS_SUPABASE_URL
export NEXT_PUBLIC_SUPABSE_PUBLIC_ANON_KEY=ECALPER_EB_OT_GNIRTS_EUQINU_YREV_EMOS_SUPABSE_PUBLIC_ANON_KEY
pnpm run build
pnpm run export
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
if: ${{ github.ref == 'refs/heads/master' }}
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: output-docker-tag
shell: bash
id: docker-tag
run: |
unset BRANCH_NAME
BRANCH_NAME=`echo $GITHUB_REF | cut -d '/' -f3 -`
echo "BRANCH_NAME: $BRANCH_NAME"
unset IMG_TAGS ; if [[ $BRANCH_NAME == "master" ]]; then IMG_TAGS='latest' ; else IMG_TAGS="$BRANCH_NAME"; fi
SHA_TAG=`echo $GITHUB_SHA | head -c 7`
echo "SHA_TAG: $SHA_TAG"
echo ::set-output name=DOCKER_BRANCH_TAG::${IMG_TAGS}
echo ::set-output name=DOCKER_SHA_TAG::${SHA_TAG}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: Dockerfile-ci
push: ${{ github.ref == 'refs/heads/master' }}
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
tags: fengkx/plastic-editor:${{steps.docker-tag.outputs.DOCKER_BRANCH_TAG}},fengkx/plastic-editor:${{steps.docker-tag.outputs.DOCKER_SHA_TAG}}
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM node:lts AS builder
RUN apt update && apt install -y git build-essential
WORKDIR /app
COPY package.json pnpm-lock.yaml /app/
RUN npm i -g pnpm
RUN pnpm install --frozen-lockfile
COPY . /app/
ARG NEXT_PUBLIC_SUPABASE_URL=ECALPER_EB_OT_GNIRTS_EUQINU_YREV_EMOS_SUPABASE_URL
ARG NEXT_PUBLIC_SUPABSE_PUBLIC_ANON_KEY=ECALPER_EB_OT_GNIRTS_EUQINU_YREV_EMOS_SUPABSE_PUBLIC_ANON_KEY
RUN pnpm run build && pnpm run export

FROM ranadeeppolavarapu/nginx-http3:latest
ENV NGINX_ENVSUBST_OUTPUT_DIR /etc/nginx

COPY nginx/nginx.conf /etc/nginx/templates/nginx.conf.template
COPY nginx/site-common.conf /etc/nginx/site-common.conf
COPY --from=builder /app/out/ /var/www/static/
COPY docker-entry-static.sh /app/docker-entry-static.sh
WORKDIR /var/www/static
CMD ["sh", "/app/docker-entry-static.sh"]
9 changes: 9 additions & 0 deletions Dockerfile-ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM ranadeeppolavarapu/nginx-http3:latest
ENV NGINX_ENVSUBST_OUTPUT_DIR /etc/nginx

COPY nginx/nginx.conf /etc/nginx/templates/nginx.conf.template
COPY nginx/site-common.conf /etc/nginx/site-common.conf
COPY out/ /var/www/static/
COPY docker-entry-static.sh /app/docker-entry-static.sh
WORKDIR /var/www/static
CMD ["sh", "/app/docker-entry-static.sh"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
A block-based note app.


# Prior Art

- https://github.com/djyde/plastic-editor/
- https://github.com/pmndrs/jotai
22 changes: 14 additions & 8 deletions atom-util/atomWithDebouncedStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ import type { PrimitiveAtom, SetStateAction, WritableAtom } from "jotai";
import { atom } from "jotai";
import _debounce from "lodash.debounce";

type Unsubscribe = () => void;
export type Unsubscribe = () => void;

type Storage<Value> = {
export type Storage<Value> = {
getItem: (key: string) => Value | Promise<Value>;
setItem: (key: string, newValue: Value) => void | Promise<void>;
delayInit?: boolean;
subscribe?: (key: string, callback: (value: Value) => void) => Unsubscribe;
};

type StringStorage = {
getItem: (key: string) => string | null | Promise<string | null>;
setItem: (key: string, newValue: string) => void | Promise<void>;
export type SimpleStorage<Value> = {
getItem: (key: string) => Value | null | Promise<Value | null>;
setItem: (key: string, newValue: Value) => void | Promise<void>;
};

export const createJSONStorage = (
export type StringStorage = SimpleStorage<string>;

export const createJSONStorage = <T extends any>(
getStringStorage: () => StringStorage
): Storage<unknown> => ({
getItem: (key) => {
Expand All @@ -27,6 +29,7 @@ export const createJSONStorage = (
return JSON.parse(value || "");
},
setItem: (key, newValue) => {
// @ts-ignore
getStringStorage().setItem(key, JSON.stringify(newValue));
},
});
Expand All @@ -39,13 +42,16 @@ export function atomWithDebouncedStorage<Value>(
isStaleAtom: WritableAtom<boolean, boolean>,
wait: number,
debounceOptions: Parameters<typeof _debounce>[2],
storage: Storage<Value> = defaultStorage as Storage<Value>
storage: Storage<Value> = defaultStorage as Storage<Value>,
fallback: boolean = false
): PrimitiveAtom<Value> {
const getInitialValue = () => {
try {
const value = storage.getItem(key);
if (value instanceof Promise) {
return value.catch(() => initialValue);
return value
.then((v) => (fallback ? v ?? initialValue : v))
.catch(() => initialValue);
}
return value;
} catch {
Expand Down
5 changes: 5 additions & 0 deletions components/404.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Error from "next/error";

export default function NotFound() {
return <Error statusCode={404} />;
}
26 changes: 15 additions & 11 deletions components/Block/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Suspense } from "react";
import type { ShallowBlock } from "@plastic-editor/protocol/lib/protocol";
import { useMountEffect, useSafeState } from "@react-hookz/web";
import clsx from "clsx";
Expand All @@ -11,6 +12,7 @@ import { ID_LEN } from "../Editor/adapters/memory";
import { editingBlockIdAtom } from "../Editor/store";
import { BlockContent } from "./BlockContent";
import { LineDirection } from "./LineDirection";
import { DotFlashing } from "../Loading";
export type PropsType = {
debugMode?: boolean;
path: number[];
Expand Down Expand Up @@ -100,17 +102,19 @@ const BlockImpl: React.FC<PropsType> = ({
<>
<div id={shallowBlock.id} className={className} ref={blockRootRef}>
<LineDirection dragRef={drag} />
<BlockContent
className={clsx({
"drop-over": isOver,
up: dropMoveDirection === "up",
down: dropMoveDirection === "down",
})}
path={path}
pageId={pageId}
shallowBlock={shallowBlock}
nextBlockId={nanoid(ID_LEN)}
/>
<Suspense fallback={<DotFlashing />}>
<BlockContent
className={clsx({
"drop-over": isOver,
up: dropMoveDirection === "up",
down: dropMoveDirection === "down",
})}
path={path}
pageId={pageId}
shallowBlock={shallowBlock}
nextBlockId={nanoid(ID_LEN)}
/>
</Suspense>
</div>
{shallowBlock.children.length > 0 && (
<BlockChildren
Expand Down
22 changes: 17 additions & 5 deletions components/Editor/adapters/AdapterContext.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { createContext, useContext } from "react";
import { hasSupabase, supabase } from "../../../db";
import { memoryAdapter } from "./memory";
import { supbaseAdapter } from "./supabase";

const AdapterContext = createContext(memoryAdapter);
type IAdapter = typeof memoryAdapter | typeof supbaseAdapter;
const AdapterContext = createContext<IAdapter>(memoryAdapter);

export type PropsType = {
adapter: typeof memoryAdapter;
adapter?: IAdapter;
};
export const AdapterProvider: React.FC<PropsType> = ({ adapter, children }) => {
export const AdapterProvider: React.FC<PropsType> = ({ children, adapter }) => {
if (!adapter) {
if (hasSupabase) {
const session = supabase.auth.session();
console.log(session, Boolean(session));
adapter = Boolean(session) ? supbaseAdapter : memoryAdapter;
} else {
adapter = memoryAdapter;
}
}
return (
<AdapterContext.Provider value={adapter}>
{children}
</AdapterContext.Provider>
);
};

export function useAdapter() {
return useContext(AdapterContext);
export function useAdapter<T extends IAdapter>() {
return useContext(AdapterContext) as T;
}
50 changes: 27 additions & 23 deletions components/Editor/adapters/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,14 @@ import { nanoid } from "nanoid";
import { NextRouter } from "next/router";
import { atomWithDebouncedStorage } from "../../../atom-util/atomWithDebouncedStorage";
import { anchorOffsetAtom, editingBlockIdAtom } from "../store";
import { Note } from "./types";
import { Note, PartialPick } from "./types";

export const ID_LEN = 15;
const DEBOUNCE_WAIT = 500;
const DEBOUNCE_MAX_WAIT = 2000;

const isStaleAtom = atom(false);

type PartialPick<T, K extends keyof T> = {
[P in K]?: T[P];
};

const defaultPageIdFromRoute = () => {
if (process.browser) {
const matches = window?.location?.pathname?.match(
Expand All @@ -44,7 +40,7 @@ const pagesAtom = atomWithDebouncedStorage<Record<string, Page>>(
DEBOUNCE_WAIT,
{ maxWait: DEBOUNCE_MAX_WAIT }
);
const pageDefault = (id: string): Page => ({
export const pageDefault = (id: string): Page => ({
id,
type: "default" as const,
title: `${format(new Date(), "MMMM, dd, yyyy")}`,
Expand All @@ -71,6 +67,9 @@ const pageFamily = atomFamily<
const shallow: ShallowBlock = { id: block.id, children: [] };
defaultValue.children = [shallow];
}
if (title) {
defaultValue.title = title;
}
return defaultValue;
},
(get, set, update) => {
Expand All @@ -88,7 +87,7 @@ const pageFamily = atomFamily<
(a, b) => a.id === b.id
);

const blockDefault = (id: string, pageId: string): Block => ({
export const blockDefault = (id: string, pageId: string): Block => ({
id,
pageId,
content: "",
Expand Down Expand Up @@ -197,19 +196,22 @@ const newBlockAtom = atom<
}
});

const pageTitleAtom = atom(
(get) => {
const title = get(pageFamily({ id: get(pageIdAtom) })).title;
return title;
},
(get, set, update) => {
const page = get(pageFamily({ id: get(pageIdAtom) }));
const newPage = produce(page, (draft) => {
draft.title = update as string;
});
set(pageFamily({ id: get(pageIdAtom) }), newPage);
const newPageAtom = atom<
null,
{
newPageId: string;
title: string;
children?: ShallowBlock[];
goto?: boolean;
}
);
>(null, (get, set, update) => {
const { newPageId, title, children, goto } = update;
const newPageAtom = pageFamily({ id: newPageId, title, children });
set(newPageAtom, get(newPageAtom));
if (goto) {
set(pageIdAtom, newPageId);
}
});

const moveBlockAtom = atom<null, { from: number[]; to: number[] }>(
null,
Expand All @@ -229,9 +231,11 @@ const moveBlockAtom = atom<null, { from: number[]; to: number[] }>(
}
);

const pageValuesAtom = atom((get) => {
return Object.values(get(pagesAtom));
});
const pageValuesAtom = atom<(Partial<Page> & Pick<Page, "title" | "id">)[]>(
(get) => {
return Object.values(get(pagesAtom));
}
);

const saveNotesAtom = atom(null, (get) => {
const pages = Object.values(get(pagesAtom));
Expand Down Expand Up @@ -307,13 +311,13 @@ export const memoryAdapter = {
moveBlockAtom,
deleteBlockAtom,
newBlockAtom,
newPageAtom,
usePage,
useBlock,
saveNotesAtom,
loadNotesAtom,
starsAtom,
gotoPageAtom,
isStaleAtom,
pageTitleAtom,
pageValuesAtom,
} as const;
Loading

1 comment on commit 6cfb8a7

@vercel
Copy link

@vercel vercel bot commented on 6cfb8a7 Aug 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.