Browse Source

Initial commit

Add files from create-next-app
master
TheoryOfNekomata 3 years ago
commit
e623df86f1
52 changed files with 7074 additions and 0 deletions
  1. +104
    -0
      .gitignore
  2. +34
    -0
      README.md
  3. +4
    -0
      jest.config.js
  4. +2
    -0
      next-env.d.ts
  5. +3
    -0
      next.config.js
  6. +31
    -0
      package.json
  7. BIN
      public/favicon.ico
  8. BIN
      public/fonts/mononoki/mononoki-Bold.eot
  9. BIN
      public/fonts/mononoki/mononoki-Bold.ttf
  10. BIN
      public/fonts/mononoki/mononoki-Bold.woff
  11. BIN
      public/fonts/mononoki/mononoki-Bold.woff2
  12. BIN
      public/fonts/mononoki/mononoki-BoldItalic.eot
  13. BIN
      public/fonts/mononoki/mononoki-BoldItalic.ttf
  14. BIN
      public/fonts/mononoki/mononoki-BoldItalic.woff
  15. BIN
      public/fonts/mononoki/mononoki-BoldItalic.woff2
  16. BIN
      public/fonts/mononoki/mononoki-Italic.eot
  17. BIN
      public/fonts/mononoki/mononoki-Italic.ttf
  18. BIN
      public/fonts/mononoki/mononoki-Italic.woff
  19. BIN
      public/fonts/mononoki/mononoki-Italic.woff2
  20. BIN
      public/fonts/mononoki/mononoki-Regular.eot
  21. BIN
      public/fonts/mononoki/mononoki-Regular.ttf
  22. BIN
      public/fonts/mononoki/mononoki-Regular.woff
  23. BIN
      public/fonts/mononoki/mononoki-Regular.woff2
  24. +77
    -0
      public/global.css
  25. +166
    -0
      public/theme.css
  26. +19
    -0
      public/theme/dark.css
  27. +19
    -0
      public/theme/light.css
  28. +4
    -0
      public/vercel.svg
  29. +41
    -0
      src/components/molecules/Brand/index.tsx
  30. +90
    -0
      src/components/molecules/Editor/index.tsx
  31. +68
    -0
      src/components/molecules/FolderLinkContent/index.tsx
  32. +39
    -0
      src/components/molecules/Link/index.tsx
  33. +63
    -0
      src/components/molecules/NoteLinkContent/index.tsx
  34. +83
    -0
      src/components/templates/BinView/index.tsx
  35. +232
    -0
      src/components/templates/NoteView/index.tsx
  36. +7
    -0
      src/models/Folder.ts
  37. +15
    -0
      src/models/Note.ts
  38. +11
    -0
      src/models/NoteVersion.ts
  39. +9
    -0
      src/models/User.ts
  40. +9
    -0
      src/models/UserLink.ts
  41. +11
    -0
      src/models/UserProfile.ts
  42. +5
    -0
      src/pages/_app.tsx
  43. +61
    -0
      src/pages/_document.tsx
  44. +19
    -0
      src/pages/index.tsx
  45. +28
    -0
      src/pages/my/bin/index.tsx
  46. +85
    -0
      src/pages/my/notes/[noteId].tsx
  47. +75
    -0
      src/pages/my/notes/index.tsx
  48. +19
    -0
      src/utils/content.test.ts
  49. +160
    -0
      src/utils/content.ts
  50. +8
    -0
      src/utils/query.ts
  51. +29
    -0
      tsconfig.json
  52. +5444
    -0
      yarn.lock

+ 104
- 0
.gitignore View File

@@ -0,0 +1,104 @@
/node_modules
/.pnp
.pnp.js
/coverage
/.next/
/out/
/build
.DS_Store
*.pem
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.env.local
.env.development.local
.env.test.local
.env.production.local
.env
.vercel
.idea/
logs
*.log
lerna-debug.log*
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
pids
*.pid
*.seed
*.pid.lock
lib-cov
coverage
*.lcov
.nyc_output
.grunt
bower_components
.lock-wscript
build/Release
node_modules/
jspm_packages/
web_modules/
*.tsbuildinfo
.npm
.eslintcache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
.node_repl_history
*.tgz
.yarn-integrity
.env.test
.cache
.parcel-cache
.next
out
.nuxt
dist
.cache/
.vuepress/dist
.serverless/
.fusebox/
.dynamodb/
.tern-port
.vscode-test
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
*.stackdump
[Dd]esktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msix
*.msm
*.msp
*.lnk
cmake-build-*/
*.iws
out/
.idea_modules/
atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
.AppleDouble
.LSOverride
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

+ 34
- 0
README.md View File

@@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.ts`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

+ 4
- 0
jest.config.js View File

@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

+ 2
- 0
next-env.d.ts View File

@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />

+ 3
- 0
next.config.js View File

@@ -0,0 +1,3 @@
module.exports = {
basePath: '',
}

+ 31
- 0
package.json View File

@@ -0,0 +1,31 @@
{
"name": "@zeichen/app-web",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "jest"
},
"homepage": "https://note.modal.sh",
"dependencies": {
"@tesseract-design/react-common": "^0.3.0",
"@tesseract-design/viewfinder": "0.1.1",
"mobiledoc-kit": "^0.13.2",
"next": "10.2.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-mobiledoc-editor": "^0.11.1",
"styled-components": "5.2.3"
},
"devDependencies": {
"@types/jest": "^26.0.23",
"@types/node": "^15.0.1",
"@types/react": "^17.0.4",
"@types/styled-components": "^5.1.9",
"jest": "^26.6.3",
"ts-jest": "^26.5.5",
"typescript": "^4.2.4"
}
}

BIN
public/favicon.ico View File

Before After

BIN
public/fonts/mononoki/mononoki-Bold.eot View File


BIN
public/fonts/mononoki/mononoki-Bold.ttf View File


BIN
public/fonts/mononoki/mononoki-Bold.woff View File


BIN
public/fonts/mononoki/mononoki-Bold.woff2 View File


BIN
public/fonts/mononoki/mononoki-BoldItalic.eot View File


BIN
public/fonts/mononoki/mononoki-BoldItalic.ttf View File


BIN
public/fonts/mononoki/mononoki-BoldItalic.woff View File


BIN
public/fonts/mononoki/mononoki-BoldItalic.woff2 View File


BIN
public/fonts/mononoki/mononoki-Italic.eot View File


BIN
public/fonts/mononoki/mononoki-Italic.ttf View File


BIN
public/fonts/mononoki/mononoki-Italic.woff View File


BIN
public/fonts/mononoki/mononoki-Italic.woff2 View File


BIN
public/fonts/mononoki/mononoki-Regular.eot View File


BIN
public/fonts/mononoki/mononoki-Regular.ttf View File


BIN
public/fonts/mononoki/mononoki-Regular.woff View File


BIN
public/fonts/mononoki/mononoki-Regular.woff2 View File


+ 77
- 0
public/global.css View File

@@ -0,0 +1,77 @@
body {
margin: 0;
/* overflow: overlay; */
}

h1 {
font-size: 3em;
text-transform: lowercase;
}

h2 {
font-size: 2em;
text-transform: lowercase;
}

h3 {
font-size: 1.75em;
text-transform: lowercase;
}

h4 {
font-size: 1.5em;
text-transform: lowercase;
}

h5 {
font-size: 1.25em;
text-transform: lowercase;
}

h6 {
font-size: 1.125em;
text-transform: lowercase;
}

p {
margin: 2em 0;
}

li {
margin: 2em 0;
}

small {
font-size: 0.75em;
}

a:focus {
outline: 0;
}

pre {
overflow: auto;
margin: 0 -1rem;
padding: 0 1rem;
line-height: 1.2;
box-sizing: border-box;
}

@media only print {
pre, pre * {
color: inherit !important;
}

code, code * {
color: inherit !important;
}

img {
filter: grayscale(100%);
}

:root {
--color-accent: black !important;
--color-active: black !important;
}
}

+ 166
- 0
public/theme.css View File

@@ -0,0 +1,166 @@
@font-face {
font-family: 'mononoki';
font-weight: 400;
font-style: normal;
font-display: swap;
src:
local('mononoki'),
url(fonts/mononoki/mononoki-Regular.woff2) format('woff2');
}

@font-face {
font-family: 'mononoki';
font-weight: 700;
font-style: normal;
font-display: swap;
src:
local('mononoki Bold'),
local('mononoki'),
url(fonts/mononoki/mononoki-Bold.woff2) format('woff2');
}

@font-face {
font-family: 'mononoki';
font-weight: 400;
font-style: italic;
font-display: swap;
src:
local('mononoki Italic'),
local('mononoki'),
url(fonts/mononoki/mononoki-Italic.woff2) format('woff2');
}

@font-face {
font-family: 'mononoki';
font-weight: 700;
font-style: italic;
font-display: swap;
src:
local('mononoki Bold Italic'),
local('mononoki BoldItalic'),
local('mononoki'),
url(fonts/mononoki/mononoki-BoldItalic.woff2) format('woff2');
}

:root {
--font-family-base: 'Encode Sans', system-ui;
--font-stretch-base: semi-expanded;
--font-weight-base: 400;
--line-height-base: 1.75em;
--font-family-headings: 'Encode Sans', system-ui;
--font-stretch-headings: condensed;
--font-weight-headings: 100;
--line-height-headings: 1.125em;
--font-family-monospace: 'mononoki';
--font-size-root: 16px;
--opacity-light: 0.25;
--opacity-lighter: 0.5;
--opacity-lightest: 0.75;
}

:root {
--color-bg: var(--color-negative, white);
--color-fg: var(--color-positive, black);
--color-accent: var(--color-primary, blue);
--color-active: var(--color-secondary, red);
}

@media (prefers-color-scheme: dark) {
:root {
--color-bg: var(--color-negative, black);
--color-fg: var(--color-positive, white);
}
}

:root {
font-size: var(--font-size-root);
font-family: var(--font-family-base), sans-serif;
font-stretch: var(--font-stretch-base, normal);
font-weight: var(--font-weight-base, 400);
line-height: var(--line-height-base, 1.75em);
transition-property: color, background-color;
transition-timing-function: ease;
transition-duration: 350ms;
}

@media only screen {
:root {
background-color: var(--color-bg);
color: var(--color-fg);
}
}

h1 {
font-family: var(--font-family-headings), sans-serif;
font-stretch: var(--font-stretch-headings, normal);
font-weight: var(--font-weight-headings, 400);
line-height: var(--line-height-headings, 1.5);
}

h2 {
font-family: var(--font-family-headings), sans-serif;
font-stretch: var(--font-stretch-headings, normal);
font-weight: calc(var(--font-weight-headings, 400) / 100 * 150);
line-height: var(--line-height-headings, 1.5);
}

h3 {
font-family: var(--font-family-headings), sans-serif;
font-stretch: var(--font-stretch-headings, normal);
font-weight: calc(var(--font-weight-headings, 400) / 100 * 250);
line-height: var(--line-height-headings, 1.5);
}

h4 {
font-family: var(--font-family-headings), sans-serif;
font-stretch: var(--font-stretch-headings, normal);
font-weight: var(--font-weight-headings, 400);
line-height: var(--line-height-headings, 1.5);
}

h5 {
font-family: var(--font-family-headings), sans-serif;
font-stretch: var(--font-stretch-headings, normal);
font-weight: var(--font-weight-headings, 400);
line-height: var(--line-height-headings, 1.5);
}

h6 {
font-family: var(--font-family-headings), sans-serif;
font-stretch: var(--font-stretch-headings, normal);
font-weight: var(--font-weight-headings, 400);
line-height: var(--line-height-headings, 1.5);
}

code {
font-family: var(--font-family-monospace), monospace;
}

pre {
font-family: var(--font-family-monospace), monospace;
}

a {
color: inherit;
text-decoration: none;
}

@media only screen {
a {
color: var(--color-accent);
text-decoration: underline;
}

a:focus {
color: var(--color-active);
}
}

::selection {
background-color: var(--color-active);
color: var(--color-fg);
}

:root {
caret-color: var(--color-active);
}

+ 19
- 0
public/theme/dark.css View File

@@ -0,0 +1,19 @@
:root {
--color-shade: #000;
--color-negative: #222;
--color-positive: #eee;
--color-primary: #C78AB3;
--color-secondary: #f90;
--color-code-number: #74f95e;
--color-code-keyword: #ff4389;
--color-code-type: #5097D2;
--color-code-instance-attribute: #76a7d2;
--color-code-function: #67c252;
--color-code-parameter: #915ec2;
--color-code-property: #ffa1c9;
--color-code-string: #eed371;
--color-code-variable: #8bc275;
--color-code-regexp: #74A72B;
--color-code-url: #0099CC;
--color-code-global: #C28050;
}

+ 19
- 0
public/theme/light.css View File

@@ -0,0 +1,19 @@
:root {
--color-shade: #fff;
--color-negative: #f8f8f8;
--color-positive: #333;
--color-primary: #ba6a9c;
--color-secondary: #f90;
--color-code-number: #72b507;
--color-code-keyword: #ee5189;
--color-code-type: #427fb1;
--color-code-instance-attribute: #76a7d2;
--color-code-function: #5a984a;
--color-code-parameter: #915ec2;
--color-code-property: #b76e8d;
--color-code-string: #b59e36;
--color-code-variable: #61864e;
--color-code-regexp: #4f7e03;
--color-code-url: #0099CC;
--color-code-global: #C28050;
}

+ 4
- 0
public/vercel.svg View File

@@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

+ 41
- 0
src/components/molecules/Brand/index.tsx View File

@@ -0,0 +1,41 @@
import Link from '../Link'
import styled from 'styled-components'

const BrandBase = styled(Link)({
display: 'block',
textDecoration: 'none',
fontSize: '1.5rem',
fontWeight: 'bold',
fontStretch: '75%',
textTransform: 'uppercase',
width: '2rem',
textAlign: 'center',
'@media (min-width: 720px)': {
width: '8rem',
textAlign: 'left',
},
})

const Hide = styled('span')({
display: 'none',
'@media (min-width: 720px)': {
display: 'inline',
},
})

const Brand = () => {
return (
<BrandBase
href={{
pathname: '/',
}}
>
Z
<Hide>
eichen
</Hide>
</BrandBase>
)
}

export default Brand

+ 90
- 0
src/components/molecules/Editor/index.tsx View File

@@ -0,0 +1,90 @@
import * as React from 'react'
import * as T from '@tesseract-design/react-common'
import {Container, Editor as MobiledocEditor, MarkupButton, LinkButton} from 'react-mobiledoc-editor'
import styled, {CSSObject} from 'styled-components'

const TOOLBAR_BUTTON_COMMON_STYLES: CSSObject = {
border: 0,
backgroundColor: 'transparent',
padding: 0,
margin: 0,
height: '3rem',
width: '3rem',
color: 'inherit',
font: 'inherit',
}

const StyledMarkupButton = styled(MarkupButton)(TOOLBAR_BUTTON_COMMON_STYLES)

const StyledLinkButton = styled(LinkButton)(TOOLBAR_BUTTON_COMMON_STYLES)

const RawInput = styled('textarea')({
color: 'inherit',
font: 'inherit',
padding: 0,
border: 0,
backgroundColor: 'transparent',
outline: 0,
display: 'block',
width: '100%',
resize: 'vertical',
})

const ToolbarBase = styled('div')({
display: 'flex',
alignItems: 'center',
flexWrap: 'wrap',
margin: '1rem 0',
})

const StyledEditor = styled('div')({
'> *': {
outline: 0,
},
})

const Editor = () => {
const [hydrated, setHydrated] = React.useState(false)
React.useEffect(() => {
setHydrated(true)
}, [])

if (hydrated) {
return (
<Container>
<ToolbarBase>
<StyledMarkupButton
tag="strong"
>
<T.Icon
name="bold"
/>
</StyledMarkupButton>
<StyledMarkupButton
tag="em"
>
<T.Icon
name="italic"
/>
</StyledMarkupButton>
<StyledLinkButton>
<T.Icon
name="link"
/>
</StyledLinkButton>
</ToolbarBase>
<StyledEditor>
<MobiledocEditor />
</StyledEditor>
</Container>
)
}

return (
<RawInput
rows={20}
/>
)
}

export default Editor

+ 68
- 0
src/components/molecules/FolderLinkContent/index.tsx View File

@@ -0,0 +1,68 @@
import * as React from 'react'
import * as T from '@tesseract-design/react-common'
import styled from 'styled-components'

const Base = styled('div')({
display: 'flex',
alignItems: 'center',
height: '4rem',
})

const Content = styled('div')({
lineHeight: 1.5,
flex: 'auto',
})

const Title = styled('strong')({
display: 'block',
whiteSpace: 'nowrap',
})

const Subtitle = styled('small')({
display: 'block',
whiteSpace: 'nowrap',
})

const IconContainer = styled('div')({
marginRight: '1rem',
})

type Props = {
title: string,
subtitle?: string,
}

const FolderLinkContent: React.FC<Props> = ({
title,
subtitle,
}) => {
return (
<Base>
<IconContainer>
<T.Icon
name="folder"
/>
</IconContainer>
<Content>
<Title>
{title}
</Title>
{
typeof (subtitle as string)
&& (
<Subtitle>
{subtitle}
</Subtitle>
)
}
</Content>
<div>
<T.Icon
name="chevron-right"
/>
</div>
</Base>
)
}

export default FolderLinkContent

+ 39
- 0
src/components/molecules/Link/index.tsx View File

@@ -0,0 +1,39 @@
import * as React from 'react'
import NextLink from 'next/link'
import {UrlObject} from 'url'

type Props = {
href: UrlObject,
as?: UrlObject,
prefetch?: boolean,
replace?: boolean,
shallow?: boolean,
component?: React.ElementType,
}

const Link: React.FC<Props> = ({
href,
as,
prefetch,
replace,
shallow,
component: Component = 'a',
...etcProps
}) => {
return (
<NextLink
href={href}
as={as}
passHref
replace={replace}
shallow={shallow}
prefetch={prefetch}
>
<Component
{...etcProps}
/>
</NextLink>
)
}

export default Link

+ 63
- 0
src/components/molecules/NoteLinkContent/index.tsx View File

@@ -0,0 +1,63 @@
import * as React from 'react'
import * as T from '@tesseract-design/react-common'
import styled from 'styled-components'

const Base = styled('div')({
display: 'flex',
alignItems: 'center',
height: '4rem',
})

const Content = styled('div')({
lineHeight: 1.5,
flex: 'auto',
})

const Title = styled('strong')({
display: 'block',
whiteSpace: 'nowrap',
})

const Subtitle = styled('small')({
display: 'block',
whiteSpace: 'nowrap',
})

const IconContainer = styled('div')({
marginRight: '1rem',
})

type Props = {
title: string,
subtitle?: string,
}

const NoteLinkContent: React.FC<Props> = ({
title,
subtitle,
}) => {
return (
<Base>
<IconContainer>
<T.Icon
name="file-text"
/>
</IconContainer>
<Content>
<Title>
{title}
</Title>
{
typeof (subtitle as string)
&& (
<Subtitle>
{subtitle}
</Subtitle>
)
}
</Content>
</Base>
)
}

export default NoteLinkContent

+ 83
- 0
src/components/templates/BinView/index.tsx View File

@@ -0,0 +1,83 @@
import * as React from 'react'
import {LeftSidebarWithMenu} from '@tesseract-design/viewfinder'
import Link from '../../molecules/Link'
import {Subpage} from '../../../utils/query'

type Props = {
subpage: Subpage,
}

const BinView: React.FC<Props> = ({
subpage
}) => {
return (
<LeftSidebarWithMenu.Layout
brand={"Brand"}
sidebarMenuItems={[
{
id: 'notes',
url: {
pathname: '/my/notes',
},
label: 'Notes',
icon: 'N',
},
{
id: 'bin',
url: {
pathname: '/my/bin'
},
icon: 'B',
label: 'Bin',
secondary: true,
},
]}
sidebarMainOpen={subpage === Subpage.SIDEBAR}
moreItemsOpen={subpage === Subpage.MORE}
sidebarMain={
<LeftSidebarWithMenu.SidebarMainContainer>
Sidebar
</LeftSidebarWithMenu.SidebarMainContainer>
}
moreLinkMenuItem={{
label: 'More',
icon: 'M',
url: {
query: {
subpage: 'more',
}
}
}}
moreLinkComponent={({ url, icon, label, }) => (
<Link
href={url}
>
<LeftSidebarWithMenu.MoreSidebarMenuContainer>
<LeftSidebarWithMenu.MoreSidebarMenuItemIcon>
{icon}
</LeftSidebarWithMenu.MoreSidebarMenuItemIcon>
{label}
</LeftSidebarWithMenu.MoreSidebarMenuContainer>
</Link>
)}
linkComponent={({ url, icon, label, }) => (
<Link
href={url}
>
<LeftSidebarWithMenu.SidebarMenuContainer>
<LeftSidebarWithMenu.SidebarMenuItemIcon>
{icon}
</LeftSidebarWithMenu.SidebarMenuItemIcon>
{label}
</LeftSidebarWithMenu.SidebarMenuContainer>
</Link>
)}
>
<LeftSidebarWithMenu.ContentContainer>
Hello
</LeftSidebarWithMenu.ContentContainer>
</LeftSidebarWithMenu.Layout>
)
}

export default BinView

+ 232
- 0
src/components/templates/NoteView/index.tsx View File

@@ -0,0 +1,232 @@
import * as React from 'react'
import Head from 'next/head'
import * as T from '@tesseract-design/react-common'
import {LeftSidebarWithMenu} from '@tesseract-design/viewfinder'
import styled from 'styled-components'
import Link from '../../molecules/Link'
import {Subpage} from '../../../utils/query'
import Note from '../../../models/Note'
import Brand from '../../molecules/Brand'
import NoteLinkContent from '../../molecules/NoteLinkContent'
import Folder from '../../../models/Folder'
import FolderLinkContent from '../../molecules/FolderLinkContent'
import Editor from '../../molecules/Editor'

const SidebarLink = styled(Link)({
textDecoration: 'none',
})

const CenteredContent = styled(LeftSidebarWithMenu.ContentContainer)({
height: 'calc(100vh - var(--height-topbar, 4rem) - var(--size-menu, 4rem))',
display: 'grid',
placeContent: 'center',
textAlign: 'center',
'@media (min-width: 720px)': {
height: 'calc(100vh - var(--height-topbar, 4rem))',
},
})

// @ts-ignore
const TitleInput = styled('input')({
color: 'inherit',
font: 'inherit',
fontSize: '3rem',
padding: 0,
border: 0,
backgroundColor: 'transparent',
fontFamily: 'var(--font-family-headings), sans-serif',
fontStretch: 'var(--font-stretch-headings, normal)',
fontWeight: 'var(--font-weight-headings, 400)',
height: '4rem',
outline: 0,
width: '100%',
display: 'block',
marginBottom: '1rem',
})

const SidebarTitle = styled('h1')({
margin: 0,
lineHeight: '4rem',
})

const TitleContainer = styled(LeftSidebarWithMenu.ContentContainer)({
display: 'block',
})

const SidebarSubtitle = styled('p')({
margin: '2rem 0',
})

type Props = {
subpage: Subpage,
notes: Note[],
subfolders: Folder[],
currentFolder: Folder,
currentNote?: Note,
}

const NoteView: React.FC<Props> = ({
subpage,
notes,
subfolders,
currentFolder,
currentNote,
}) => {
const APP_NAME = 'Zeichen'

return (
<>
<Head>
<title>
{currentNote ? `${currentNote.title} | ${APP_NAME}` : APP_NAME}
</title>
</Head>
<LeftSidebarWithMenu.Layout
brand={
<Brand />
}
sidebarMenuItems={[
{
id: 'notes',
url: {
pathname: '/my/notes',
},
label: 'Notes',
icon: (
<T.Icon
name="book"
/>
),
},
{
id: 'bin',
url: {
pathname: '/my/bin'
},
icon: (
<T.Icon
name="trash2"
/>
),
label: 'Bin',
secondary: true,
},
]}
sidebarMainOpen={subpage === Subpage.SIDEBAR}
moreItemsOpen={subpage === Subpage.MORE}
sidebarMain={
<>
{currentFolder && (
<LeftSidebarWithMenu.SidebarMainContainer>
<SidebarTitle>
{currentFolder.name}
</SidebarTitle>
{currentFolder.description && (
<SidebarSubtitle>
{currentFolder.description}
</SidebarSubtitle>
)}
</LeftSidebarWithMenu.SidebarMainContainer>
)}
{subfolders.map(f => (
<SidebarLink
href={{
pathname: '/my/notes',
query: {
folderId: f.id,
},
}}
>
<LeftSidebarWithMenu.SidebarMainContainer>
<FolderLinkContent
title={f.name}
/>
</LeftSidebarWithMenu.SidebarMainContainer>
</SidebarLink>
))}
{notes.map(n => (
<SidebarLink
href={{
pathname: '/my/notes/[noteId]',
query: {
noteId: n.id,
},
}}
>
<LeftSidebarWithMenu.SidebarMainContainer>
<NoteLinkContent
title={n.title}
subtitle={n.contentVersions[0].createdAt.toString()}
/>
</LeftSidebarWithMenu.SidebarMainContainer>
</SidebarLink>
))}
</>
}
moreLinkMenuItem={{
label: 'More',
icon: (
<T.Icon
name="more-horizontal"
/>
),
url: {
query: {
subpage: 'more',
}
}
}}
moreLinkComponent={({ url, icon, label, }) => (
<Link
href={url}
>
<LeftSidebarWithMenu.MoreSidebarMenuContainer>
<LeftSidebarWithMenu.MoreSidebarMenuItemIcon>
{icon}
</LeftSidebarWithMenu.MoreSidebarMenuItemIcon>
{label}
</LeftSidebarWithMenu.MoreSidebarMenuContainer>
</Link>
)}
linkComponent={({ url, icon, label, }) => (
<Link
href={url}
>
<LeftSidebarWithMenu.SidebarMenuContainer>
<LeftSidebarWithMenu.SidebarMenuItemIcon>
{icon}
</LeftSidebarWithMenu.SidebarMenuItemIcon>
{label}
</LeftSidebarWithMenu.SidebarMenuContainer>
</Link>
)}
>
{!currentNote && (
<CenteredContent>
<T.Icon
name="folder"
size="4rem"
/>
Select a note from the menu.
</CenteredContent>
)}
{currentNote && (
<>
<TitleContainer
as="label"
>
<TitleInput
placeholder="Title"
/>
</TitleContainer>
<LeftSidebarWithMenu.ContentContainer>
<Editor />
</LeftSidebarWithMenu.ContentContainer>
</>
)}
</LeftSidebarWithMenu.Layout>
</>
)
}

export default NoteView

+ 7
- 0
src/models/Folder.ts View File

@@ -0,0 +1,7 @@
export default class Folder {
id: string

name: string

description?: string
}

+ 15
- 0
src/models/Note.ts View File

@@ -0,0 +1,15 @@
import Folder from './Folder'
import User from './User'
import NoteVersion from './NoteVersion'

export default class Note {
id: string

title: string

folder?: Folder

authorUser: User

contentVersions: NoteVersion[]
}

+ 11
- 0
src/models/NoteVersion.ts View File

@@ -0,0 +1,11 @@
import Note from './Note'

export default class NoteVersion {
id: string

content: string

createdAt: Date | string

parent?: NoteVersion
}

+ 9
- 0
src/models/User.ts View File

@@ -0,0 +1,9 @@
import UserProfile from './UserProfile'

export default class User {
id: string

username: string

profile: UserProfile
}

+ 9
- 0
src/models/UserLink.ts View File

@@ -0,0 +1,9 @@
export default class UserLink {
id: string

title: string

description?: string

url: string
}

+ 11
- 0
src/models/UserProfile.ts View File

@@ -0,0 +1,11 @@
import UserLink from './UserLink'

export default class UserProfile {
id: string

email: string

bio?: string

links: UserLink[]
}

+ 5
- 0
src/pages/_app.tsx View File

@@ -0,0 +1,5 @@
const MyApp = ({ Component, pageProps }) => {
return <Component {...pageProps} />
}

export default MyApp

+ 61
- 0
src/pages/_document.tsx View File

@@ -0,0 +1,61 @@
import Document, {Html, Head, Main, NextScript} from 'next/document'
import {ServerStyleSheet} from 'styled-components'
import pkg from '../../package.json'
import config from '../../next.config'

const publicUrl = process.env.NODE_ENV === 'production' ? pkg.homepage : config.basePath

export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage

try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(
<App
{...props}
/>,
),
})

const initialProps = await Document.getInitialProps(ctx)

return {
...initialProps,
styles: (
<>
{initialProps.styles}
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Encode+Sans:wdth,wght@75..112.5,100..900&display=swap" />
<link rel="stylesheet" href={`${publicUrl}/global.css`} />
<link rel="stylesheet" href={`${publicUrl}/theme.css`} />
<link rel="stylesheet" title="Dark" href={`${publicUrl}/theme/dark.css`} />
<link rel="alternate stylesheet" title="Light" href={`${publicUrl}/theme/light.css`} />
{sheet.getStyleElement()}
</>
),
}
} catch (err) {
console.error(err)
} finally {
sheet.seal()
}
}

render() {
return (
<Html
lang="en-PH"
>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

+ 19
- 0
src/pages/index.tsx View File

@@ -0,0 +1,19 @@
import {GetServerSideProps, NextPage} from 'next'

type Props = {}

const Page: NextPage<Props> = () => {
return (
<>
Hello
</>
);
};

export default Page

export const getServerSideProps: GetServerSideProps = async (ctx) => {
return {
props: {},
}
}

+ 28
- 0
src/pages/my/bin/index.tsx View File

@@ -0,0 +1,28 @@
import {GetServerSideProps, NextPage} from 'next'
import BinView from '../../../components/templates/BinView'
import {QueryFragment, Subpage} from '../../../utils/query'

type Props = {
subpage: Subpage,
}

const Page: NextPage<Props> = ({
subpage,
}) => {
return (
<BinView
subpage={subpage}
/>
);
};

export default Page

export const getServerSideProps: GetServerSideProps = async (ctx) => {
const { [QueryFragment.SUBPAGE]: subpage = '' } = ctx.query
return {
props: {
subpage,
},
}
}

+ 85
- 0
src/pages/my/notes/[noteId].tsx View File

@@ -0,0 +1,85 @@
import {GetServerSideProps, NextPage} from 'next'
import NoteView from '../../../components/templates/NoteView'
import {QueryFragment, Subpage} from '../../../utils/query'
import Note from '../../../models/Note'
import Folder from '../../../models/Folder'

type Props = {
subpage: Subpage,
notes: Note[],
currentFolder?: Folder,
subfolders: Folder[],
currentNote: Note,
}

const Page: NextPage<Props> = ({
subpage,
notes,
currentFolder,
subfolders,
currentNote,
}) => {
return (
<NoteView
subpage={subpage}
notes={notes}
subfolders={subfolders}
currentFolder={currentFolder}
currentNote={currentNote}
/>
);
};

export default Page

export const getServerSideProps: GetServerSideProps = async (ctx) => {
const { [QueryFragment.SUBPAGE]: subpage = '', noteId, } = ctx.query
const authorUser = {
id: '0',
profile: {
id: '0',
email: 'hello@example.com',
links: [],
},
username: 'johndoe'
}
const notes: Note[] = [
{
id: '0',
title: 'Hello',
authorUser: authorUser,
contentVersions: [
{
id: '0',
content: 'Note content',
createdAt: new Date().toISOString(),
}
],
}
]
const subfolders: Folder[] = [
{
id: '0',
name: 'Child Folder',
description: 'Where we put other notes',
}
]
const currentNote: Note = {
id: noteId as string,
title: 'This Note',
authorUser,
contentVersions: [],
}
return {
props: {
subpage,
notes,
subfolders,
currentFolder: {
name: 'Root Folder',
description: 'Default location of your notes.',
},
currentNote,
},
}
}

+ 75
- 0
src/pages/my/notes/index.tsx View File

@@ -0,0 +1,75 @@
import {GetServerSideProps, NextPage} from 'next'
import NoteView from '../../../components/templates/NoteView'
import {QueryFragment, Subpage} from '../../../utils/query'
import Note from '../../../models/Note'
import Folder from '../../../models/Folder'

type Props = {
subpage: Subpage,
notes: Note[],
currentFolder?: Folder,
subfolders: Folder[],
}

const Page: NextPage<Props> = ({
subpage,
notes,
currentFolder,
subfolders,
}) => {
return (
<NoteView
subpage={subpage}
notes={notes}
subfolders={subfolders}
currentFolder={currentFolder}
/>
);
};

export default Page

export const getServerSideProps: GetServerSideProps = async (ctx) => {
const { [QueryFragment.SUBPAGE]: subpage = '' } = ctx.query
const authorUser = {
id: '0',
profile: {
id: '0',
email: 'hello@example.com',
links: [],
},
username: 'johndoe'
}
const notes: Note[] = [
{
id: '0',
title: 'Hello',
authorUser: authorUser,
contentVersions: [
{
id: '0',
content: 'Note content',
createdAt: new Date().toISOString(),
}
],
}
]
const subfolders: Folder[] = [
{
id: '0',
name: 'Child Folder',
description: 'Where we put other notes',
}
]
return {
props: {
subpage,
notes,
subfolders,
currentFolder: {
name: 'Root Folder',
description: 'Default location of your notes.',
},
},
}
}

+ 19
- 0
src/utils/content.test.ts View File

@@ -0,0 +1,19 @@
import {deserialize} from './content'

describe('deserialize', () => {
it('should parse BBCode', () => {
expect(deserialize(`
hello world
`)).toEqual({
version: '0.3.2',
markups: [],
atoms: [],
cards: [],
sections: [
[1, 'p', [
[0, [], 0, 'hello world']
]]
],
})
})
})

+ 160
- 0
src/utils/content.ts View File

@@ -0,0 +1,160 @@
enum MarkerType {
TEXT = 0,
ATOM = 1,
}

enum SectionType {
MARKUP = 1,
IMAGE = 2,
LIST = 3,
CARD = 10,
}

// TODO!!!!!
// Try parsing BBCode before converting it to Mobiledoc and vv
const generateBBCodeAST = (bbcode: string) => (
bbcode
.trim()
.split('')
.reduce(
(parserState, c) => {
const {tokenStack, tagStack, tree} = parserState
const [lastToken] = tokenStack.slice(-1)
switch (c) {
case '[':
return {
...parserState,
tokenStack: [
...tokenStack,
'',
],
}
case ']':
if (
lastToken.startsWith('/')
&& lastToken.slice('/'.length) === tagStack.slice(-1)[0]
) {
return {
...parserState,
tokenStack: tokenStack.slice(0, -1),
tagStack: tagStack.slice(0, -1),
}
}
return {
...parserState,
tokenStack: tokenStack.slice(0, -1),
tagStack: [
...tagStack,
lastToken,
]
}
default:
break
}

return {
...parserState,
tokenStack: [
...tokenStack.slice(0, -1),
lastToken + c,
],
}
},
{
tree: [],
tokenStack: [''],
tagStack: [],
}
)
)

const convertBBCodeToMobiledoc = (bbcode: string, { components, version = '0.3.2,' }) => {
return bbcode
.trim()
.split('')
.reduce(
(parserState, c) => {
switch (c) {
case '[':
return {
...parserState,
tokenStack: [
...parserState.tokenStack,
'',
]
}
case ']':
if (parserState.lastToken.startsWith('/')) {
return {
...parserState,
// pop token stack
tokenStack: parserState.tokenStack.slice(0, -1),
tagStack: parserState.tagStack.slice(0, -1),
}
}
return {
...parserState,
// pop token stack
tokenStack: parserState.tokenStack.slice(0, -1),
tagStack: [
...parserState.tagStack,
parserState.lastToken,
],
lastToken: '',
}
}

let [lastSection = [SectionType.MARKUP, 'p', []]] = parserState.state.sections.slice(-1)
let [sectionType = SectionType.MARKUP, sectionTag = 'p', sectionMarkers = []] = lastSection
let [lastMarker = [MarkerType.TEXT, [], 0, '']] = sectionMarkers.slice(-1)
let [textTypeIdentifier, openMarkupsIndexes, numberOfClosedMarkups, value] = lastMarker

return {
...parserState,
sections: [
...parserState.state.sections,
[
sectionType,
sectionTag,
[
...sectionMarkers,
[
textTypeIdentifier,
openMarkupsIndexes,
numberOfClosedMarkups,
value + parserState.lastCharacter,
],
],
],
],
lastCharacter: c,
}
},
{
state: {
version,
markups: [],
atoms: [],
cards: [],
sections: [],
},
lastCharacter: '',
lastToken: '',
tokenStack: [],
tagStack: [],
}
)
.state
}

const convertMobiledocToBBCode = (mobiledoc: any, { components }) => {

}

export const deserialize = (text: string) => {
return convertBBCodeToMobiledoc(text, { components: [] })
}

export const serialize = (data: any) => {
return convertMobiledocToBBCode(data, { components: [] })
}

+ 8
- 0
src/utils/query.ts View File

@@ -0,0 +1,8 @@
export enum Subpage {
SIDEBAR = 'sidebar',
MORE = 'more',
}

export enum QueryFragment {
SUBPAGE = 'subpage',
}

+ 29
- 0
tsconfig.json View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}

+ 5444
- 0
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save