{
54 | if (onContentChange) {
55 | const currentText = divRef.current.innerText;
56 | if (currentText !== text) {
57 | setText(currentText);
58 | onContentChange(currentText);
59 | }
60 | }
61 | }}
62 | />
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/styles/_bootstrap.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v4.6.0 (https://getbootstrap.com/)
3 | * Copyright 2011-2021 The Bootstrap Authors
4 | * Copyright 2011-2021 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
6 | */
7 |
8 | @import "~bootstrap/scss/root";
9 | @import "~bootstrap/scss/reboot";
10 | @import "~bootstrap/scss/type";
11 | @import "~bootstrap/scss/images";
12 | @import "~bootstrap/scss/code";
13 | @import "~bootstrap/scss/grid";
14 | @import "~bootstrap/scss/tables";
15 | @import "~bootstrap/scss/forms";
16 | @import "~bootstrap/scss/buttons";
17 | @import "~bootstrap/scss/transitions";
18 | @import "~bootstrap/scss/dropdown";
19 | @import "~bootstrap/scss/button-group";
20 | @import "~bootstrap/scss/input-group";
21 | @import "~bootstrap/scss/custom-forms";
22 | @import "~bootstrap/scss/nav";
23 | @import "~bootstrap/scss/navbar";
24 | @import "~bootstrap/scss/card";
25 | @import "~bootstrap/scss/breadcrumb";
26 | @import "~bootstrap/scss/pagination";
27 | @import "~bootstrap/scss/badge";
28 | @import "~bootstrap/scss/jumbotron";
29 | @import "~bootstrap/scss/alert";
30 | @import "~bootstrap/scss/progress";
31 | @import "~bootstrap/scss/media";
32 | @import "~bootstrap/scss/list-group";
33 | @import "~bootstrap/scss/close";
34 | @import "~bootstrap/scss/toasts";
35 | @import "~bootstrap/scss/modal";
36 | @import "~bootstrap/scss/tooltip";
37 | @import "~bootstrap/scss/popover";
38 | @import "~bootstrap/scss/carousel";
39 | @import "~bootstrap/scss/spinners";
40 | @import "~bootstrap/scss/utilities";
41 | @import "~bootstrap/scss/print";
42 |
43 | @import "~bootswatch/dist/pulse/bootswatch";
44 |
45 | // Add backdrop-filter to modals
46 | .modal {
47 | backdrop-filter: blur(4px);
48 | }
49 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Head from "next/head";
3 | import { AppProps } from "next/app";
4 | import "@/styles/main.scss";
5 | import { AuthProvider } from "@/services/firebase/auth";
6 | import { initializeApp } from "@/services/firebase";
7 | import * as day from "dayjs";
8 | import RelativeTime from "dayjs/plugin/relativeTime";
9 |
10 | day.extend(RelativeTime);
11 |
12 | if (typeof window !== "undefined") {
13 | initializeApp();
14 | }
15 |
16 | export default function App({ Component, pageProps }: AppProps): JSX.Element {
17 | return (
18 | <>
19 |
20 |
24 |
25 |
29 |
30 | {/* Created using https://favicon.io/favicon-generator/ */}
31 |
36 |
42 |
48 |
49 |
javelin
50 |
54 |
55 |
56 |
57 |
58 | >
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/services/firebase/auth.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import firebase from "firebase/app";
3 | import { createDebug } from "@/utils/log";
4 |
5 | export interface Auth {
6 | uid: string;
7 | email: string;
8 | displayName: string;
9 | photoURL: string;
10 | }
11 |
12 | const debug = createDebug("firebase-auth");
13 |
14 | const AuthContext = React.createContext
(false);
15 |
16 | function createAuth(user: firebase.User) {
17 | return {
18 | uid: user.uid,
19 | email: user.email,
20 | displayName: user.displayName,
21 | photoURL: user.photoURL,
22 | };
23 | }
24 |
25 | export function AuthProvider({
26 | children,
27 | }: {
28 | children: React.ReactNode;
29 | }): JSX.Element {
30 | const [auth, setAuth] = React.useState(false);
31 |
32 | React.useEffect(() => {
33 | const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
34 | setAuth(user ? createAuth(user) : null);
35 | debug("auth state changed");
36 | });
37 |
38 | return () => {
39 | unsubscribe();
40 | setAuth(false);
41 | };
42 | }, []);
43 |
44 | return {children};
45 | }
46 |
47 | export function useAuth(): false | Auth {
48 | return React.useContext(AuthContext);
49 | }
50 |
51 | export function useAuthorize(
52 | authorize = (auth: Auth) => Boolean(auth)
53 | ): boolean {
54 | const auth = useAuth();
55 |
56 | return auth === false ? null : Boolean(auth) && authorize(auth);
57 | }
58 |
59 | export async function signIn(): Promise {
60 | const provider = new firebase.auth.GoogleAuthProvider();
61 | provider.addScope("email");
62 |
63 | const credential = await firebase.auth().signInWithPopup(provider);
64 | debug("sign in");
65 |
66 | return createAuth(credential.user);
67 | }
68 |
69 | export async function signOut(): Promise {
70 | await firebase.auth().signOut();
71 | debug("sign out");
72 | }
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # javelin
2 |
3 | [](https://www.pixiv.net/en/artworks/73661871)
4 |
5 | > I've gotten closer to you now, Commander. Hehehe. But I'm gonna have to work
6 | > harder~
7 |
8 | javelin is an app where people can arrange notes in a number of columns. It is
9 | built with [Next.js][next] and uses [Firebase][firebase] to store data.
10 |
11 | A public instance is available here: https://javelin.vercel.app.
12 |
13 | ## Usage
14 |
15 | ### Requirements
16 |
17 | - Node.js 14
18 | - A Firebase project to use (free tier is enough)
19 |
20 | ### Installation
21 |
22 | Clone this repository, then install the dependencies:
23 |
24 | ```bash
25 | git clone https://github.com/tkesgar/javelin
26 | cd javelin
27 | npm install
28 | ```
29 |
30 | Get the Firebase project configuration and add it in `.env` as
31 | `NEXT_PUBLIC_FIREBASE_CONFIG`. Otherwise it will uses the public javelin
32 | Firebase instance.
33 |
34 | ```
35 | NEXT_PUBLIC_FIREBASE_CONFIG="{
36 | "apiKey":"firebase-api-key","authDomain": "firebase-auth-domain.firebaseapp.com",
37 | "databaseURL": "https://firebase-database-url.firebaseio.com",
38 | "projectId": "firebase-project-id",
39 | "storageBucket": "firebase-storage-bucket.appspot.com",
40 | "messagingSenderId": "firebase-messaging-sender-id",
41 | "appId": "firebase-app-id"
42 | }"
43 | ```
44 |
45 | ### Development
46 |
47 | ```bash
48 | npm run dev
49 | ```
50 |
51 | ### Deployment
52 |
53 | ```
54 | npm run build
55 | npm start
56 | ```
57 |
58 | ## Contributing
59 |
60 | Feel free to submit [issues] and create [pull requests][pulls].
61 |
62 | ## License
63 |
64 | Licensed under MIT License.
65 |
66 |
67 | [firebase]: https://firebase.google.com/
68 | [issues]: https://github.com/tkesgar/javelin/issues
69 | [pulls]: https://github.com/tkesgar/javelin/pulls
70 |
71 |
--------------------------------------------------------------------------------
/features/board/components/RemoveButton.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Button } from "react-bootstrap";
3 | import { AlertCircle, Trash2 } from "react-feather";
4 | import style from "./RemoveButton.module.scss";
5 | import classnames from "classnames";
6 |
7 | type RemoveButtonProps = React.ComponentPropsWithRef