Compare commits

...

420 commits

Author SHA1 Message Date
Max Leiter
0c20460c13 /new page responsiveness 2023-07-20 20:48:02 -07:00
Max Leiter
563136fdb3 fix markdown checkbox colors 2023-07-20 20:24:10 -07:00
dependabot[bot]
bff0dbea38
Bump word-wrap from 1.2.3 to 1.2.4 (#151)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 19:13:41 -07:00
Max Leiter
5d5fd3182e
Shadify (#150)
Adds shadcn, bumps dependencies, overhaults lots of code.
2023-07-20 18:04:47 -07:00
PeGaSuS
702f59caf8
Add systemd units (#148)
Added systemd units, both for use as root and normal user
2023-06-10 12:02:24 -07:00
Max Leiter
41e72ba04c Bump next 2023-05-30 13:12:28 -07:00
Max Leiter
0df85776a5 Bump next 2023-05-26 13:34:40 -04:00
Max Leiter
7f4745ade1 Add loading fallback state for date picker 2023-05-21 14:06:51 -07:00
Max Leiter
504d2742f4 post-list <li> a11y improvements 2023-05-20 15:50:57 -07:00
Max Leiter
69ca511cc2 revalidate home page 2023-05-20 15:49:11 -07:00
Max Leiter
a1fa7dbb8a remove @vercel/og, use bundled package 2023-05-20 15:47:37 -07:00
Max Leiter
c416f5d5e8 migrate header info back to client-side 2023-05-20 15:42:21 -07:00
Max Leiter
dc11f8eb0c bump next and deps, fix header buttons 2023-05-20 15:16:50 -07:00
David Schultz
5e4ecbb803
Fix isAdmin check to be by role and not uid (#147)
* isAdmin should be based on role, not uid

* move admin link before sign in/out
2023-05-20 12:35:33 -07:00
Zdeněk Janeček
0f58c44261
fix: build errors (#145) 2023-05-13 00:00:47 -07:00
Max Leiter
d0a73a7cbc fix sign out, theme switch, auth provider icons 2023-04-13 20:13:08 -07:00
Zdeněk Janeček
55e19381a7
fix: add keys to header buttons (#142)
Co-authored-by: Zdeněk Janeček <zdenek.janecek@firma.seznam.cz>
2023-03-28 09:38:05 -07:00
Zdeněk Janeček
3433371930
feat: add keycloak login (#139)
Co-authored-by: Zdeněk Janeček <zdenek.janecek@firma.seznam.cz>
2023-03-28 00:37:27 -07:00
Max Leiter
7887b42404
components/header: rm memo from RSC (#141) 2023-03-28 00:37:10 -07:00
Max Leiter
68fc679864 SSR theme and auth in header. Move auth check to middleware. Bump next 2023-03-27 20:16:11 -07:00
Max Leiter
cb7d9ebc6b Bump next.js and deps 2023-03-01 20:11:48 -08:00
Max Leiter
3041da80e2 Fix useSessionSWR() expecting Drift's API interface instead of next-auths 2023-02-26 18:49:32 -08:00
Max Leiter
a54a22f142 chore: linting 2023-02-26 15:22:05 -08:00
Max Leiter
27a604dc90 fix building by adjusting types 2023-02-26 15:21:43 -08:00
Max Leiter
6cf544fc72 opt-out tests-backup from eslint/tsc 2023-02-26 14:46:04 -08:00
Max Leiter
86e323fbca Add some (WIP) tests 2023-02-26 14:44:32 -08:00
Max Leiter
b64281b1ac Slight shell script improvements 2023-02-26 01:21:08 -08:00
Max Leiter
9c3375cbd0 Add shell script for uploading files to drift 2023-02-26 01:15:17 -08:00
Max Leiter
806b173d22 Make CmdK dynamic 2023-02-25 22:46:55 -08:00
Max Leiter
590cc51ec8 Migrate some CSS to nested 2023-02-25 22:43:11 -08:00
Max Leiter
85f21bf505 Add cmdk, nested postcss 2023-02-25 22:36:29 -08:00
Max Leiter
cc2215629d lint, internally refactor header 2023-02-25 16:29:03 -08:00
Max Leiter
aaf2761004 add total-typescript ts-reset 2023-02-25 16:27:38 -08:00
Max Leiter
88c65f2e9f Bump deps 2023-02-25 16:27:23 -08:00
Max Leiter
6d184906b1 Switch to new Metadata API 2023-02-24 18:07:40 -08:00
Max Leiter
afd18d19a9 Remove /home rewrite, replace with page 2023-02-23 23:56:13 -08:00
Max Leiter
f2d42a6c0c Add ErrorBoundary to /mine, remove header underline 2023-02-23 23:46:26 -08:00
Max Leiter
0b6d31373d Add toast for revoked api key 2023-02-23 23:45:26 -08:00
Max Leiter
d1e5dca3d0 markdown: use --link for link color 2023-02-23 23:44:41 -08:00
Max Leiter
a5704e0a6d @next/font -> next/font 2023-02-23 23:44:02 -08:00
Max Leiter
86c2fb4a73 mobile header improvements 2023-02-23 20:40:07 -08:00
Max Leiter
fec58f2465 dep bumps, add pages feature, bug fixes, type improvements 2023-02-23 20:35:25 -08:00
Max Leiter
e21d896669 Improve list item skeleton on desktop 2023-02-21 23:48:34 -08:00
Max Leiter
9a811e85b5 chore: lint 2023-02-21 23:34:09 -08:00
Max Leiter
817a12fffb Add .env.default, support VERCEL_URL in lib/config 2023-02-21 23:27:03 -08:00
Max Leiter
e51815eb16 Bump next.js and related deps 2023-02-21 23:20:10 -08:00
Max Leiter
64cfe9033e Add (unworking) route handler for raw files 2023-02-21 23:19:55 -08:00
Max Leiter
072516bdb0 temporarily switch to /api/ route for raw files 2023-02-21 22:52:51 -08:00
Max Leiter
b5b4bf08f6 Fix post creation/text input 2023-01-29 00:31:24 -08:00
Max Leiter
98cbbf2347 Add suspense boundar around useSearchParams 2023-01-29 00:24:55 -08:00
Max Leiter
c813ffaf56 convert datepicker to next/dynamic 2023-01-29 00:14:04 -08:00
Max Leiter
eda977b203 ts fixes 2023-01-28 23:58:00 -08:00
Max Leiter
f3d588c0eb button style improvements, homepage and navbar refactors 2023-01-28 23:53:45 -08:00
Max Leiter
a64cc78eed Add loading.tsx for mine 2023-01-28 23:52:04 -08:00
Max Leiter
1acbb52e27 Add react-loading-boundary and error component 2023-01-28 23:51:40 -08:00
Max Leiter
3048d842de Add stack component 2023-01-28 23:51:22 -08:00
Max Leiter
41a7a90bda unstable_getServerSession -> getServerSession 2023-01-28 23:51:14 -08:00
Max Leiter
08abdd4642 bump deps 2023-01-28 21:58:21 -08:00
Max Leiter
be73154b4e some file cleanup, add api/og function 2023-01-23 21:05:17 -08:00
Max Leiter
acfcc04af4 Apply proper align-items to tabs on small screens 2023-01-13 15:50:31 -08:00
Max Leiter
f5e2fd365b Bump next.js to 13.1.2 stable 2023-01-13 15:50:18 -08:00
Max Leiter
16b4a5ae07 Re-implement public search 2023-01-13 00:17:50 -08:00
Max Leiter
ba092152f2 remove use of VERCEL_URL 2023-01-13 00:10:15 -08:00
Max Leiter
7c8e2c9947 Remove unused params 2023-01-13 00:08:23 -08:00
Max Leiter
5b7efc8a06 Fix post searching 2023-01-13 00:01:32 -08:00
Max Leiter
e49ca2e749 Linting, component clean up 2023-01-12 23:56:04 -08:00
Max Leiter
ba732dcd71 refactor to SWR and verifyApiUser; personal post search is broken 2023-01-12 20:50:59 -08:00
Max Leiter
6fb81d77b9 Fix building error 2023-01-07 15:55:25 -08:00
Max Leiter
6b0a6bf3b6 Fix showing password prompt for unauthed protected posts 2023-01-07 15:42:11 -08:00
Max Leiter
a6c8c8c825 Fix signing into correct user with credentials 2023-01-07 15:30:07 -08:00
Max Leiter
6148f8d1e9 Switch default buttonType to secondary 2023-01-07 15:14:56 -08:00
Max Leiter
c51ca39fa7 Eslint changes and linting (no-mix-spaces) 2023-01-07 14:52:27 -08:00
Max Leiter
371dae25d9 README updates for src refactor 2023-01-07 14:37:31 -08:00
Max Leiter
6bbd380392 Rm use-session 2023-01-07 13:04:07 -08:00
Max Leiter
d9e7aa5ecf Bug fixes, code cleanup, made root dir / 2023-01-07 13:02:52 -08:00
Max Leiter
c21ca52a59
README: fix logo path 2023-01-06 12:06:40 -08:00
Max Leiter
98ad33bcd8 API tokens 2023-01-05 21:05:49 -08:00
Max Leiter
b9ab0df7c0 Some styling fixes, potentially fix building w/ docker 2022-12-29 13:50:49 -05:00
Max Leiter
02695345cd formatting icons: refactor CSS/remove borders 2022-12-25 23:00:13 -08:00
Max Leiter
69a40df606 remove unnecessary CSS and !importants 2022-12-25 21:03:24 -08:00
Max Leiter
6aa5301d89 lib/server: move making 1st user admin to next auth event 2022-12-25 20:52:32 -08:00
Max Leiter
604f5d64d0 post/[id]: move function to new file to avoid invalid-segment-export 2022-12-25 20:07:10 -08:00
Max Leiter
b848aa9e40 Style improvements, re-enable themes, bump next 2022-12-25 20:00:26 -08:00
Max Leiter
e41dc292b8 Center icon and text in post list li 2022-12-18 18:35:55 -08:00
Max Leiter
e4b215b7a8 Fix post-list linting 2022-12-18 18:21:12 -08:00
Max Leiter
19c5725847 Add eslint configs, fix lint errors 2022-12-18 18:18:32 -08:00
Max Leiter
631f98aaaf Add @next/font, increase markdown legibility 2022-12-18 17:09:46 -08:00
Max Leiter
a97ba1b9aa Default credential auth to true, readme updates 2022-12-18 13:53:25 -08:00
Max Leiter
f07f4789ee cleanup view-document styling in jsx 2022-12-18 13:46:52 -08:00
Max Leiter
23a850253b File styling adjustments 2022-12-18 01:14:46 -08:00
Max Leiter
5e976bfc0d Fix export from segment config 2022-12-17 23:15:35 -08:00
Max Leiter
3e199cf8d4 remove unnecessary comment 2022-12-17 23:11:45 -08:00
Max Leiter
447974a74a Add deleting users to admin, refactor auth 2022-12-17 23:09:47 -08:00
Max Leiter
65cf59e96b Add credentials provider, fix header active style 2022-12-17 19:42:48 -08:00
Max Leiter
0631ae3897 Fix more types 2022-12-17 17:38:45 -08:00
Max Leiter
82aadd94f2 Use Prisma type utils 2022-12-17 17:30:17 -08:00
Max Leiter
69c482a165 Switch post html/content to Bytes from Text 2022-12-17 17:15:21 -08:00
Max Leiter
34a92a265f Fix /new/from posts 2022-12-17 16:28:49 -08:00
Max Leiter
ff310a67b9 Page/layout optimizations, bump next, styling fixes 2022-12-17 16:22:29 -08:00
Max Leiter
f034f29a1d bump react-hot-toasts to beta 2022-12-16 12:38:45 -08:00
Max Leiter
350575ccd4 intentionally break post rendering 2022-12-16 11:49:10 -08:00
Max Leiter
70212232a0 rename client to src 2022-12-09 19:30:19 -08:00
Max Leiter
aee2330e21 Fix building by addressing missing labels/props for inputs 2022-12-05 16:42:36 -08:00
Max Leiter
7ef45c28f0 Add public post listing to home page 2022-12-04 14:49:18 -08:00
Max Leiter
5918b13867 lint 2022-12-04 14:26:14 -08:00
Max Leiter
72633c6ad2 More uniform home page spacing, close mobile menu on click 2022-12-04 14:26:05 -08:00
Max Leiter
a84dad1dde Update readme to mention refactor branch 2022-12-04 02:07:12 -08:00
Max Leiter
330dbd85b1 bump next-themes 2022-12-04 02:04:47 -08:00
Max Leiter
7eeadbe065 Add basic /author/{id} page 2022-12-04 01:55:20 -08:00
Max Leiter
56eefc8419 add basic admin page, misc fixes 2022-12-04 01:31:51 -08:00
Max Leiter
9b593c849e Bump next and next-themes 2022-12-01 19:59:05 -08:00
Max Leiter
8578714c4a Fix SSG by moving auth from root layout 2022-12-01 19:45:19 -08:00
Max Leiter
44a05f6456 fix /new 2022-11-29 22:22:17 -08:00
Max Leiter
ce0c442273 Remove geist-ui, add loading prop to button, convert header to CSS 2022-11-29 22:10:51 -08:00
Max Leiter
fc79f7df4d bump next, use next-themes, remove geist icons and most of geist core 2022-11-29 00:43:08 -08:00
Max Leiter
23b7343963 bump lockfile 2022-11-28 21:01:22 -08:00
Max Leiter
1d7db6e059 rm vscode settings 2022-11-28 21:00:12 -08:00
Max Leiter
a7660f6374 add prisma to reg deps 2022-11-28 20:59:30 -08:00
Max Leiter
8048e99794 Fix linting issues 2022-11-28 18:36:11 -08:00
Max Leiter
d6894ffb8b remove more of geist-ui: add spinner, button dropdown, toasts. bump deps 2022-11-28 18:33:06 -08:00
Max Leiter
0cab3acd62 enforce label or aria-label on input props 2022-11-20 21:02:13 -08:00
Max Leiter
41ed505362 fix preview rendering on /home, fix signout redirect 2022-11-20 20:54:34 -08:00
Max Leiter
97e4742453 bump next, use custom tabs on /home 2022-11-20 19:26:07 -08:00
Max Leiter
881e693e76 title: don't re-render when updating post 2022-11-17 23:39:52 -08:00
Max Leiter
8fe7299258 fix error badge color 2022-11-17 23:01:48 -08:00
Max Leiter
12d9eafcd9 lint 2022-11-17 22:36:53 -08:00
Max Leiter
4cf448c35d more geist removal; add popover, convert more of post editing and viewing 2022-11-16 02:16:56 -08:00
Max Leiter
3c5dcc24ac Custom tabs 2022-11-16 00:49:12 -08:00
Max Leiter
45c2e59105 use custom input/buttons on postlist/new page 2022-11-15 22:52:25 -08:00
Max Leiter
dfe0d39fa0 remove next-themes, convert header to custom button 2022-11-15 20:50:54 -08:00
Max Leiter
bff7c90e5f Convert card from geist, badge style improvements 2022-11-15 19:07:07 -08:00
Max Leiter
3bebb6ac7d README updates 2022-11-14 19:00:21 -08:00
Max Leiter
2c3e271df1 only run prisma admin middleware if enable_admin 2022-11-14 18:49:58 -08:00
Max Leiter
aef1788747 rm server/ 2022-11-14 18:46:24 -08:00
Max Leiter
37d4dfebcf fix building 2022-11-14 18:45:06 -08:00
Max Leiter
e1ef002300 move react-datepicker.css 2022-11-14 18:40:16 -08:00
Max Leiter
8e7828d562 fix middleware, migrate gist importing 2022-11-14 18:39:42 -08:00
Max Leiter
bc2a4acd29 Fix admin page styling and user table 2022-11-14 17:32:32 -08:00
Max Leiter
c5e276b51c rm old constants 2022-11-14 17:26:37 -08:00
Max Leiter
c31b911c86 md table styles, expiration post fixes, dont render linenumbers 2022-11-14 17:24:35 -08:00
Max Leiter
2b36e3c58e fix admin 404 2022-11-14 01:34:17 -08:00
Max Leiter
f81999241f health check API route 2022-11-14 01:30:09 -08:00
Max Leiter
2b783145d4 fix admin page, expiring view, displayName setting/field 2022-11-14 01:28:40 -08:00
Max Leiter
0627ab7396 fix raw file viewing, rm password from settings, add admin api 2022-11-13 23:28:51 -08:00
Max Leiter
97cff7eb53 use custom badge component, add post deletion 2022-11-13 23:02:31 -08:00
Max Leiter
5f4749ebb3 radix tooltip, move header to layout 2022-11-12 18:39:03 -08:00
Max Leiter
733a93dd87 dep management 2022-11-12 17:19:27 -08:00
Max Leiter
ecd4521403 refactor getting html for files and previews 2022-11-12 17:11:05 -08:00
Max Leiter
c41cf7c5ef rm server code, add markdown rendering, html saving, visibility updating 2022-11-12 16:06:23 -08:00
Max Leiter
096cf41eee gen prisma migration, fix up rendering html (somewhat) in RSC 2022-11-12 01:57:30 -08:00
Max Leiter
86b9172527 fix up colocation 2022-11-12 01:28:06 -08:00
Max Leiter
96da95818f colocate components 2022-11-12 00:58:21 -08:00
Max Leiter
96c4023c14 migrate post page and create post api, misc changes 2022-11-11 23:59:33 -08:00
Max Leiter
60d1b031f5 use next-auth, add sign in via github, switch to postgres 2022-11-11 19:17:44 -08:00
Max Leiter
7c761eb727 actually bump next 2022-11-11 16:34:35 -08:00
Max Leiter
68570b3bb7 bump next, fix background color flash 2022-11-11 16:33:43 -08:00
Max Leiter
8b0b172f7d convert admin, run lint 2022-11-09 23:11:36 -08:00
Max Leiter
cf7d89eb20 cookies fixes, hook improvement, more porting 2022-11-09 19:46:12 -08:00
Max Leiter
95d1ef31ef rework cookies 2022-11-09 19:02:06 -08:00
Max Leiter
9b9c3c1d87 start api transition, prisma additions 2022-11-09 18:38:05 -08:00
Max Leiter
da870d6957 next-themes work, add index app/ page 2022-11-08 22:14:43 -08:00
Max Leiter
6b2b8b8be6 server: upgrade sqlite 2022-11-08 00:24:18 -08:00
Max Leiter
0a5a2adb26 dep improvements, style fixes, next/link codemod 2022-11-08 00:23:28 -08:00
Max Leiter
0405f821c4 switch to pnpm 2022-11-07 21:18:40 -08:00
renovate[bot]
04ed522566
fix(deps): update all non-major dependencies (#111)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-07 12:07:50 -07:00
Max Leiter
6a951cad78
client: add more formatting icons and improve formatting bar responsiveness 2022-04-22 15:42:54 -07:00
Max Leiter
a52e9a1c62
client/server: linting and fix next building 2022-04-21 22:01:59 -07:00
Max Leiter
bceeb5cee8
Revert "client/server: lint and add functionality for admin to update homepage"
This reverts commit b5024e3f45.
2022-04-20 01:52:07 -07:00
Max Leiter
b5024e3f45
client/server: lint and add functionality for admin to update homepage 2022-04-20 01:49:34 -07:00
Max Leiter
be6de7c796
client/server: clean-up admin page, re-implement user deletion/role toggling 2022-04-19 23:36:56 -07:00
Max Leiter
3d2bec0d5e
Revert "server: switch to sqlite3 package from pinned commit"
This reverts commit 3269dfc0dc.
2022-04-19 22:19:40 -07:00
Max Leiter
a3c733f82e
server: remove unnecessary console.log 2022-04-19 22:15:40 -07:00
Max Leiter
519b6cdc71
client/server: add user settings page; add displayName, email, bio fields to user model (#108) 2022-04-19 22:14:08 -07:00
renovate[bot]
a884fdbfef
chore(deps): update all non-major dependencies (#105)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-19 18:50:21 -07:00
Max Leiter
265f7b6161
client: delete protected/ dir and SSG homepage again, but with graceful fallback (#107) 2022-04-19 18:05:21 -07:00
Max Leiter
57cded29c3
client: remove unnecessary signedIn state from post-page 2022-04-18 14:51:02 -07:00
Max Leiter
3269dfc0dc
server: switch to sqlite3 package from pinned commit 2022-04-18 14:50:08 -07:00
Max Leiter
4177897691
client: redirect unauthed users from all /new paths 2022-04-18 14:48:08 -07:00
Max Leiter
454ea303a6
client: remove get-post-path and usage of it, fix view parent btn on post page not working due to SSR 2022-04-14 17:18:47 -07:00
Max Leiter
683cad2a8d
server: lint, renovate: weekly updates 2022-04-14 14:58:56 -07:00
Joaquin "Florius" Azcarate
c0566efc98
Import a single Gist by ID (#76) 2022-04-14 14:55:36 -07:00
Max Leiter
00b03db3ef
client: lint and fix building due to missing type 2022-04-14 14:32:20 -07:00
Joaquin "Florius" Azcarate
b9d26e16f7
Change post visibility (#83)
* Change post visibility

Closes #64

* Fix imports + right align controls
2022-04-14 14:27:38 -07:00
Joaquin "Florius" Azcarate
5df56fbdae
Add description to posts (#71)
Closes #37
2022-04-14 14:25:31 -07:00
renovate[bot]
2ba613d562
chore(deps): update dependency @types/react to v18.0.5 (#103)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-14 12:42:59 -07:00
renovate[bot]
6bb73b877e
server: fix(deps): update react monorepo to v18 (#98)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-13 17:45:57 -07:00
renovate[bot]
3c2f902877
fix(deps): update all non-major dependencies (#100)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-13 17:41:54 -07:00
Max Leiter
4658d90c8c
README: add table of contents, add Running with Docker section (#101)
* README: add table of contents, add Running with Docker section

* Remove outdated REGISTRATION_PASSWORD details
2022-04-13 17:11:40 -07:00
renovate[bot]
f687152455
fix(deps): pin dependency rc-table to 7.24.1 (#99)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-13 15:44:52 -07:00
kinghat
43aa68e082
cleaned up multi-service compose (#75)
* cleaned up multi-service compose
* refactor docker setup
* add all ENVs and remove unneeded variables
* remove comments, unneeded vars and syntax
2022-04-13 15:31:38 -07:00
Max Leiter
3d747f41cc
client: remove unused param in post-table 2022-04-13 12:42:20 -07:00
Max Leiter
7ce6acf5fe
client: fix admin imports 2022-04-13 12:37:15 -07:00
Max Leiter
16103f2fcb
client: temp. remove ability to view files in posts as admin 2022-04-12 21:24:04 -07:00
Max Leiter
7d08570915
client/server: admin page improvements; add deleting users and changing roles 2022-04-12 21:14:10 -07:00
Max Leiter
7d5afbc682
client: fix building with react 18 2022-04-12 19:19:06 -07:00
Max Leiter
9df1a22aa7
client: update to react 18 2022-04-12 19:09:41 -07:00
Max Leiter
16d5780110
client: redirect /private/ and /protected/ links to /post to handle backwards compat 2022-04-12 16:54:02 -07:00
Max Leiter
67e1b9889b
client: remove need for multiple post page URLs 2022-04-12 16:48:12 -07:00
Max Leiter
4bcf791c86
Revert "fix(deps): update sqlite3 digest to 11c988c (#90)"
This reverts commit 90d9fabd27.
2022-04-12 13:55:26 -07:00
renovate[bot]
873db86fb1
fix(deps): update all non-major dependencies (#91)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-12 13:52:42 -07:00
Max Leiter
917e85196e
client: remove unused dependencies 2022-04-12 13:44:08 -07:00
renovate[bot]
9850c9a9ca
chore(deps): update actions/checkout action to v3 (#92)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-12 13:39:45 -07:00
renovate[bot]
6481de22d4
chore(deps): update actions/setup-node action to v3 (#93)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-12 13:39:33 -07:00
renovate[bot]
90d9fabd27
fix(deps): update sqlite3 digest to 11c988c (#90)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-12 13:28:04 -07:00
renovate[bot]
d17e240e1c
fix(deps): pin dependencies (#89)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-12 13:25:48 -07:00
Max Leiter
05826aa344
renovate: change group:all to group:allNonMajor 2022-04-12 13:20:44 -07:00
Max Leiter
fe589d63d8
client: mine page fixes, remove lodash.debounce 2022-04-11 23:07:52 -07:00
Max Leiter
1369bdf996
client: remove unnecessary prop in edit-document-list 2022-04-11 22:47:45 -07:00
Max Leiter
f510813e4b
client: add textarea-markdown-editor package and replace current editor textarea 2022-04-11 22:39:35 -07:00
Max Leiter
481d4ae36c
client: add / auto-increment number at end of copied posts instead of 'copy of' text 2022-04-11 19:57:41 -07:00
Max Leiter
83def0ec86
client: remove preact to fix file dropdown process crash 2022-04-10 20:05:42 -07:00
Max Leiter
401a0df63b
client: add type to please next lint 2022-04-09 17:54:08 -07:00
Max Leiter
36e255ad2b
client: lint tsx files with prettier 2022-04-09 17:48:19 -07:00
renovate[bot]
c44ab907bb
renovate: Configure Renovate (#82)
* chore(deps): add renovate.json
* renovate: group all PRs

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Max Leiter <maxwell.leiter@gmail.com>
2022-04-09 15:09:57 -07:00
dependabot[bot]
8ada3a6300
build(deps): bump minimist from 1.2.5 to 1.2.6 in /client (#80)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-09 13:19:35 -07:00
dependabot[bot]
b6af63671b
server: build(deps): bump minimist from 1.2.5 to 1.2.6 in /server (#81)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-09 13:19:18 -07:00
Max Leiter
d1415d1ee2
client: don't re-direct the owner of a file if it's expired 2022-04-08 23:30:07 -07:00
Max Leiter
9fe9b818c4
client: remove console.log from /mine 2022-04-08 22:44:46 -07:00
Max Leiter
bc6362e412
client: improve error page design, fix isPageRequest regex in middleware 2022-04-06 16:31:41 -07:00
Max Leiter
2a9e7ba6fc
server: linting 2022-04-06 15:14:05 -07:00
Max Leiter
18dff00a93
client: cleanup middleware redirect code 2022-04-06 14:42:18 -07:00
Max Leiter
5dabbbe64b
client/server: re-add removed config test line, remove unnecessary import 2022-04-06 10:48:41 -07:00
Max Leiter
2ecf1b21ca
client: remove server as build requirement
This means public/unlisted posts and the home-page are no longer
generated at build-time, so TTFB may be increased for the first user to
load/access a page. Cache-Control headers are set so if the RP / server
supports it the results should be cached for future users.
2022-04-06 10:41:42 -07:00
Max Leiter
c73b7f66a3
server: move middleware tests to middleware/__tests__, set yarn test to mute console logs 2022-04-06 09:31:16 -07:00
Max Leiter
0e57e28b6c
server: add basic jwt middleware tests 2022-04-06 09:28:01 -07:00
Max Leiter
6c39d1c7c0
server: secret-key middleware tests 2022-04-06 09:15:21 -07:00
Max Leiter
c6f89a28ad
server/client: replace JWTDenyList model with AuthToken, update middleware and routes 2022-04-06 09:08:51 -07:00
NDI Lionel
b6439858df
server: add JWTDenyList table and signout route (#52)
* add models and signout route
* add migration file for JWTDenylist

Closes #25 

Co-authored-by: Max Leiter <maxwell.leiter@gmail.com>
2022-04-06 08:39:06 -07:00
Max Leiter
9cbcfd3397
server/client: remove unnecessary console.logs 2022-04-06 08:36:20 -07:00
Max Leiter
32cc1f861e
server/client: mute vercel github comments 2022-04-05 16:25:05 -07:00
Max Leiter
808314658d
server: fix private posts being accessible for authed accounts 2022-04-05 16:22:42 -07:00
Max Leiter
e5b9b65b55
server: add basic is-admin tests and bug fixes 2022-04-05 16:17:08 -07:00
Joaquin "Florius" Azcarate
06d847dfa3
Only run the backend CI when a change happens in the server (#73) 2022-04-05 10:24:26 -07:00
Max Leiter
3a879edc23
client/server: move markdown rendering from client/ entirely to server/ 2022-04-04 18:13:18 -07:00
Joaquin "Florius" Azcarate
d495d7b222
server: replace process.env with our env thoughout (#70)
Bonus: tests!
2022-04-03 14:38:48 -07:00
Max Leiter
f8ba5b32c9
client: mobile post page adjustments with longer button bar 2022-04-03 13:09:04 -07:00
Max Leiter
f6cd545ca7
server: modify jest config to ignore node_modules, dist directories
without this, invoking `jest` would cause this warning:
jest-haste-map: Haste module naming collision: sequelize-typescript-starter
  The following files share their name; please adjust your hasteImpl:
    * <rootDir>/package.json
    * <rootDir>/dist/package.json
2022-04-03 13:02:15 -07:00
Joaquin "Florius" Azcarate
76a2b50c6b
server: throw if secretKey not set in production, set default in development (#59) 2022-04-03 12:50:04 -07:00
Joaquin "Florius" Azcarate
ef005ef0b2
server: Add first e2e test, github action, health endpoint (#68)
And a bonus health endpoint to make the simplest test possible
2022-04-03 12:47:37 -07:00
Max Leiter
5e9288e9fb
client: stop middleware from signing out on /signout pre-fetch 2022-04-02 00:53:06 -07:00
Max Leiter
52dc5e41a5
client: remove signout() function from useSignedIn
We now use the middleware-implemented /signout route
2022-04-02 00:46:39 -07:00
Max Leiter
a1fef656bb
client: refactor header component for improved SSR 2022-04-02 00:45:26 -07:00
Max Leiter
e7cec9b827
client: remove console.log 2022-04-01 23:06:07 -07:00
Max Leiter
763cb1dadc
Merge pull request #65 from MaxLeiter/dupePosts
client/server: add the ability to copy a post, view a posts parent
2022-04-01 22:58:49 -07:00
Max Leiter
b8cdc2cf72
client/server: add the ability to copy a post, view a posts parent 2022-04-01 22:55:49 -07:00
Max Leiter
ce01eba9c0
client: don't run middleware if the request isn't a page 2022-04-01 21:17:33 -07:00
Max Leiter
f927fae9ed
client: improve clicking jump to file button 2022-04-01 17:26:01 -07:00
Max Leiter
06fad98ee1
client: add new-post error if no files are present 2022-04-01 16:59:06 -07:00
Max Leiter
f20fa72b6d
client: password modal fixes for protected posts 2022-04-01 16:51:23 -07:00
Max Leiter
ead3b0af9d
server: fix accessing password protected posts 2022-04-01 16:51:07 -07:00
Max Leiter
702dad14cb
server: address deprecated sequelize logger option 2022-04-01 16:44:29 -07:00
Max Leiter
60b21a1d9d
Merge pull request #63 from MaxLeiter/configAndMigrationWork
client/server: use `config` object on server, client type fixes
2022-04-01 16:31:43 -07:00
Max Leiter
24157ff10e
server: improve error message if NODE_ENV is unknown value 2022-04-01 16:30:09 -07:00
Max Leiter
64e9c58d5d
client: remove sequelize-cli-ts 2022-04-01 16:28:42 -07:00
Max Leiter
dafc0c37f8
client/server: use config dictionary on server, improve types on client 2022-04-01 16:27:23 -07:00
Joaquin "Florius" Azcarate
8da6d62cea
Destoy files when destroying post (#62) 2022-04-01 15:52:08 -07:00
Joaquin "Florius" Azcarate
6a6a2a3496
server: drift_home already has a default (#61)
`||` on an exsiting string does nothing
2022-04-01 15:03:21 -07:00
Joaquin "Florius" Azcarate
1c2fef0ee4
server/client: use cross-env for Windows support (#58)
In order for windows to run a script with an environment variable, we need some extra steps (namely `cross-env`).
2022-04-01 14:37:24 -07:00
Joaquin "Florius" Azcarate
dee06fab90
client: Use system theme (#60)
Use resolvedTheme theme from next-theme
2022-04-01 13:36:03 -07:00
Max Leiter
e3e9d993f2
client: post page responsiveness improvements 2022-03-31 18:32:56 -07:00
Max Leiter
eae627807b
client: expiration styling improvements 2022-03-30 23:30:01 -07:00
Max Leiter
8291010f26
client: don't flash page after it's expired 2022-03-30 23:27:09 -07:00
Max Leiter
222b020e9a
client: new-post style improvements and date picker bug fixes 2022-03-30 23:03:21 -07:00
Max Leiter
5da96a8f0a
client: fix expiration input on small screens 2022-03-30 21:22:55 -07:00
Max Leiter
88d14a40b1
client: redirect on page expiration if not author 2022-03-30 20:52:30 -07:00
Max Leiter
76e7bb8013
server: allow expiresAt to be null for new posts 2022-03-30 20:12:41 -07:00
Max Leiter
47cd9cc094
Merge pull request #56 from MaxLeiter/expiringPosts
client/server: add support for expiring posts
2022-03-30 20:07:59 -07:00
Max Leiter
93e8b7e1d9
remove @types/react-datetime-picker 2022-03-30 20:03:57 -07:00
Max Leiter
9f810378f1
remove expiration modal 2022-03-30 20:02:16 -07:00
Max Leiter
752b2c0980
client/server: add support for expiring posts 2022-03-30 20:01:24 -07:00
Max Leiter
f1381e30b9
repo: .funding - funding 2022-03-30 18:33:20 -07:00
Max Leiter
9cc3db414f
repo: add FUNDING.yml 2022-03-30 18:31:44 -07:00
Max Leiter
d24e94da04
server: remove mention of sequelize config 2022-03-30 00:27:42 -07:00
Max Leiter
0504bd57e2
server: fix post 500 errors 2022-03-29 17:12:44 -07:00
Max Leiter
6de415ed99
server: fix auth 500 errors 2022-03-29 17:11:13 -07:00
Max Leiter
871b57ea3c
client: client-side validation of new post page 2022-03-29 17:06:11 -07:00
Max Leiter
009aefdb8a
remove sqlite file from git 2022-03-29 14:16:35 -07:00
Max Leiter
a84459b859
client: redirect to /new when authenticated 2022-03-29 13:22:47 -07:00
Max Leiter
62a77b619e
client: potentially fix /new redirect 2022-03-29 13:21:58 -07:00
Max Leiter
57f9966729
README: remove production usage disclaimer 2022-03-29 12:41:59 -07:00
Max Leiter
29743a67a5
README: spelling is difficult 2022-03-29 11:53:39 -07:00
Max Leiter
6f811f66a5
README: update disclaimer on functionality 2022-03-29 11:49:15 -07:00
Max Leiter
85ae8173bb
client/server: lint 2022-03-29 00:19:33 -07:00
Max Leiter
6afc4c915e
client/server: search cleanup, admin work 2022-03-29 00:11:02 -07:00
Max Leiter
7505bb43fe
README: add pm2 instructions 2022-03-28 20:57:13 -07:00
Max Leiter
fb8f14fd98
server: dockerfile updates and switch to bcryptjs 2022-03-28 19:26:16 -07:00
Max Leiter
fd7d0be6ba
server: add yarn start and building support with tsc 2022-03-28 16:19:53 -07:00
Max Leiter
333e3647e0
server: fix post creation 2022-03-28 12:29:08 -07:00
Max Leiter
e0b0102603
server: rework migrations/sequelize, add basic admin page/role, bug fixes 2022-03-28 12:13:22 -07:00
Max Leiter
1c411f3bdc
client: bump max file size to 50 MB 2022-03-28 12:04:29 -07:00
Max Leiter
ac1cf27d56
client: add more file extensions 2022-03-28 11:47:46 -07:00
Max Leiter
73e2edfe2b
server: error on post with no files 2022-03-28 11:36:46 -07:00
Max Leiter
de54754833
client: markdown style improvements 2022-03-26 22:35:34 -07:00
Max Leiter
e12e20418a
client: vertical buttongroup on post view page on mobile 2022-03-26 00:37:01 -07:00
Max Leiter
5ac73718cf
server/client: add post deletion 2022-03-26 00:24:18 -07:00
Max Leiter
62bc7af004
client: misc style improvements, error handling 2022-03-26 00:05:05 -07:00
Max Leiter
9d9f2d98a7
client: respect reduce motion when auto-scrolling 2022-03-25 14:43:39 -07:00
Max Leiter
945d3fbe63
client: show header link icon on mobile 2022-03-25 14:40:13 -07:00
Max Leiter
0815d43ee8
client: lint and minor scroll button/file explorer adjustments 2022-03-25 14:31:10 -07:00
Max Leiter
887ecfabbc
client: handle files with no names in file dropdown 2022-03-25 13:37:54 -07:00
Max Leiter
ff8d5aab5c
client: add scroll to top button on post view 2022-03-25 13:29:49 -07:00
Max Leiter
1ace04985c
client: add file dropdown to post view 2022-03-25 13:01:46 -07:00
Max Leiter
dfea957046
client: add file tree to post view 2022-03-24 20:24:40 -07:00
Max Leiter
448c443e2e
client: misc style improvements 2022-03-24 19:32:24 -07:00
Max Leiter
12f25c49a7
client: misc style improvements 2022-03-24 19:25:02 -07:00
Max Leiter
bb893fa6ba
server: sort search results 2022-03-24 18:28:41 -07:00
Max Leiter
a61f2c00e2
client: proxy html render requests so they can be cached easier 2022-03-24 18:24:09 -07:00
Max Leiter
5aca059953
client: improve post-list spacing 2022-03-24 18:15:12 -07:00
Max Leiter
7c6eb87870
client: remove filters for post search 2022-03-24 18:10:27 -07:00
Max Leiter
21332a7d1e
client: remove extra message on 'mine' page 2022-03-24 18:09:37 -07:00
Max Leiter
951088bacf
client/server: add post searching 2022-03-24 18:03:57 -07:00
Max Leiter
9949faeebd
client: improve default home message 2022-03-24 15:44:12 -07:00
Max Leiter
c1c5af2b18
README: update progress in README 2022-03-24 15:37:19 -07:00
Max Leiter
b77265e6b6
client: add client-side search of posts list 2022-03-24 15:35:59 -07:00
Max Leiter
e5f467b26a
client: improve theming variable consistency 2022-03-24 15:12:54 -07:00
Max Leiter
2823c217ea
server: add and run prettier 2022-03-24 14:57:40 -07:00
Max Leiter
056a2bd3ce
Merge pull request #40 from maxall41/better-validation
server: Better validation using celebrate
2022-03-24 14:54:52 -07:00
Max Leiter
da8e7415dc
Merge with main 2022-03-24 14:53:57 -07:00
Max Leiter
b93e42a347
markdown: re-enable code highlighting 2022-03-24 14:09:36 -07:00
Max Leiter
41238cb79f
client: lighter border color 2022-03-23 19:54:54 -07:00
Max Leiter
8b2a22e3d3
client: tweak markdown link color 2022-03-23 19:09:59 -07:00
Max Leiter
de68796101
client: disable theme transitions on change 2022-03-23 18:54:42 -07:00
Max Leiter
6a4ff9c307
client: theme fixes and adjust visibility badge spacing on post view 2022-03-23 18:47:21 -07:00
Max Leiter
6045200ac4
client: add next-themes support for CSS variables 2022-03-23 18:22:51 -07:00
Max Leiter
186d536175
client: experiment with heading title color in markdown 2022-03-23 17:14:08 -07:00
Max Leiter
7c857cf318
client: increase markdown top margin 2022-03-23 17:01:41 -07:00
Max Leiter
2a1f95238b
client: improve heading margins in markdown 2022-03-23 16:52:14 -07:00
Max Leiter
bbee452250
client: improve markdown font styles 2022-03-23 16:49:58 -07:00
Max Leiter
7ca0cbac4c
client: set post title if text pasted w/o title set 2022-03-23 16:42:56 -07:00
Max Leiter
c55ca681b4
client: improve markdown rendering 2022-03-23 16:28:39 -07:00
Max Leiter
60f2ab99b3
client: lint with useTabs 2022-03-23 15:42:22 -07:00
Max Leiter
48a8e9f6a9
client: fix hydration error with placeholder for post title 2022-03-23 15:40:47 -07:00
Max Leiter
d4120e6f41
client: add prettier, switch to preact 2022-03-23 15:34:23 -07:00
Max Leiter
9bdff8f28f
client/server: load rendered html after page load, fix raw exporting 2022-03-23 12:36:29 -07:00
Max Leiter
534cd87dc9
client: improve link accessibility in markdown 2022-03-22 23:34:33 -07:00
Max Leiter
a139acc747
client/server: render nested elements in headings 2022-03-22 23:20:40 -07:00
Max Leiter
f92d854336
client/server: linkify headers and transform html entities in markdown 2022-03-22 22:18:07 -07:00
Max Leiter
c0c18e5b61
client: improve makrdown handling of nested elements 2022-03-22 22:05:26 -07:00
Max Leiter
ef16bfc565
client: better link hover symbol 2022-03-22 21:57:11 -07:00
Max Leiter
26a9639589
client: nprogress 2022-03-22 21:37:27 -07:00
Max Leiter
0a724f6f97
client: improved code styles in markdown 2022-03-22 21:24:11 -07:00
Max Leiter
118c06f272
client: fix useless prop 2022-03-22 21:19:55 -07:00
Max Leiter
19988e49ed
server: store and render markdown on server 2022-03-22 21:18:26 -07:00
Max Leiter
30e32e33cf
client: improve markdown styles 2022-03-22 20:27:48 -07:00
Max Leiter
eaffebb53c
client: optimize fetching rendered markdown 2022-03-22 20:16:24 -07:00
Max Leiter
34b1ab979f
client: overhaul markdown rendering (now server-side), refactor theming 2022-03-22 20:06:15 -07:00
Max Leiter
d1ee9d857f
client: beging markdown rendering on server 2022-03-22 17:37:21 -07:00
Max Leiter
da46422764
client: stop some unncessary re-renders on new page 2022-03-21 22:50:25 -07:00
Max Leiter
d83cdf3eeb
client: use next/dynamic for react markdown rendering 2022-03-21 20:43:50 -07:00
Max Leiter
97f354a271
client: fix downloading zip files 2022-03-21 20:34:05 -07:00
Max Leiter
12cc8bccaa
client: refactor view page components and optimize geist-ui imports 2022-03-21 20:30:45 -07:00
Max Leiter
266848e6b2
client: fix uploading files when no files are present 2022-03-21 19:14:40 -07:00
Max Leiter
c1dcfb6a58
client: don't require confirming password accessing protected page 2022-03-21 19:01:29 -07:00
Max Leiter
ecd06a2258
client: finish protected posts 2022-03-21 18:51:19 -07:00
Max Leiter
a3130e6d2a
server: fix sqlite database location 2022-03-21 17:58:04 -07:00
Max Leiter
fb38ecc932
Merge pull request #44 from MaxLeiter/passwordPosts
Protected posts
2022-03-21 17:43:05 -07:00
Max Leiter
d30c34deec
Add usage of SECRET_KEY to secure API routes 2022-03-21 17:42:37 -07:00
Max Leiter
90fa28ad65
post generation rework with static paths/props 2022-03-21 17:20:41 -07:00
Max Leiter
3efbeb726f
client: tree-shaking improvements 2022-03-21 16:00:55 -07:00
Max Leiter
2ec4019ea2
client: fix submitting status on new post 2022-03-21 14:56:45 -07:00
Max Leiter
d06d0ffea2
client: remove cache control 2022-03-21 14:54:36 -07:00
Max Leiter
1c68aa9765
client: use cookie for theme, redirect post view in server side props 2022-03-21 14:27:48 -07:00
maxall4
87da281f1b Updated for server password option 2022-03-21 10:10:35 -10:00
maxall4
ba1efe3a9e Fixed conflicts 2022-03-21 10:09:57 -10:00
Max Leiter
e37bd00a13
Merge with main 2022-03-21 12:46:21 -07:00
Max Leiter
dc64972188
client: tsconfig and next settings 2022-03-21 12:45:35 -07:00
Max Leiter
5b3d69d4a7
Merge pull request #34 from icepaq/render-public-posts-server-side
client: render posts server side
2022-03-21 12:45:04 -07:00
Max Leiter
3f0212c5c6
begin work on protected posts 2022-03-21 03:28:06 -07:00
Max Leiter
65b0c8f7f3
client: distinguish current page in header 2022-03-21 02:20:12 -07:00
Max Leiter
bf878473af
client: change all auth redirects to middleware 2022-03-21 01:36:31 -07:00
Max Leiter
abe419daba
client: add signout route 2022-03-21 01:15:37 -07:00
Max Leiter
a5e4c0ef75
client: use next middleware for redirects/rewrites based on auth; make preview 100% height always 2022-03-21 01:03:21 -07:00
Max Leiter
594e903fe4
README: update disclaimer on current status for production use 2022-03-20 23:40:51 -07:00
Max Leiter
b84d982be2
Merge pull request #41 from MaxLeiter/database
server: enable sqlite3 database and document env vars
2022-03-21 00:30:03 -06:00
Max Leiter
c4cd55f4e6
server/client: add registration password and env vars 2022-03-20 23:27:09 -07:00
Max Leiter
e2c5e2dac9
server: enable sqlite3 database and document env vars 2022-03-20 23:10:43 -07:00
Max Leiter
c57e0d6692
client: fix logging out with new cookie auth 2022-03-20 22:34:42 -07:00
Max Leiter
3f8511e0c1
client: stop unnecessary title re-renders in /new 2022-03-20 21:43:04 -07:00
Max Leiter
2fbcb41cdd
client: clean-up drag and drop code and set post title if unset 2022-03-20 21:18:42 -07:00
Max Leiter
921f219c5a
client: add focus styling for file upload area 2022-03-20 20:54:31 -07:00
Max Leiter
9ba17db6f9
client: improve responsiveness 2022-03-20 20:46:22 -07:00
Max Campbell
8117eb8b8a Added back missing check 2022-03-20 14:51:33 -10:00
Max Campbell
dd0f38bd8b Removed console.log 2022-03-20 14:51:00 -10:00
Max Campbell
1eb998c7d6 Changes 2022-03-20 14:47:21 -10:00
Max Campbell
8324a26eb6 Changes 2022-03-20 14:47:18 -10:00
Max Campbell
d407c2f546 Made changes to validation 2022-03-20 14:46:16 -10:00
Max Campbell
7b2baad782 Added celebreate validation 2022-03-20 14:44:44 -10:00
Max Leiter
c9f84fe69c
client: remove backticks around inline code markdown blocks 2022-03-20 14:23:35 -07:00
Max Leiter
9d6db0c40b
client: improve link hover in markdown 2022-03-20 14:17:54 -07:00
Max Leiter
59d33042f2
client: fix auth errors and wrap markdown headings in links 2022-03-20 14:09:56 -07:00
Anton
c720b929ce specify renderPost, new error message, try await 2022-03-19 20:15:17 -04:00
Anton
e646df43f2 clean up post check 2022-03-16 19:21:22 -04:00
Anton
3ac9cbcf4e remove console.log 2022-03-16 18:58:43 -04:00
Anton
ac9027c522 fix wrong post check 2022-03-16 18:57:40 -04:00
Anton
7364eb668b remove client side post fetch 2022-03-16 18:54:04 -04:00
Anton
79a8f498c5 render posts server side 2022-03-15 22:49:41 -04:00
Max Leiter
d75819a02a
Merge pull request #32 from icepaq/token-as-cookie
Store token and userid as a cookie
2022-03-15 16:10:47 -06:00
Anton
a92062414f Merge branch 'store-token-in-cookies' into token-as-cookie 2022-03-15 15:27:46 -04:00
Anton
ecbd0584c2 store token and userid in a Cookie 2022-03-15 15:15:54 -04:00
Max Leiter
ed3d413eab
Merge pull request #23 from emilyst/patch-1
Minor wording change for "Download" link
2022-03-13 22:33:17 -07:00
Emily Strickland
1f0d60424e
Minor wording change for "Download" link
Suggesting this change simply because "ZIP" appears to be (per its specification published by PKWARE) canonically spelled uppercase.

I also added the word "archive" to provide a noun modified by "ZIP" rather than treating "ZIP" as a noun itself. It seems clearer this way, but may cause text flow issues in the design if it's too long.
2022-03-14 02:51:01 +00:00
Max Leiter
10c8b02397
Merge pull request #28 from boringContributor/auth-form
refactor: improve the auth component
2022-03-13 17:40:10 -07:00
Sebastian Sauerer
a361b16293 enhance auth workflow by adding a better error message and by validating the auth payload on server side 2022-03-13 20:57:55 +01:00
Sebastian Sauerer
2ba1dd7f26 refactor: improve the auth component 2022-03-13 14:07:03 +01:00
Max Leiter
80bcf68a7f
README: fix typo 2022-03-12 23:39:19 -08:00
Max Leiter
a490b94bce
Merge pull request #26 from sampaioxsamuel/fix-sign-up
Fix empty spaces in sign-in/sign-up inputs
2022-03-12 20:51:02 -08:00
Samuel
0e0c4e36ac add path alias 2022-03-13 01:40:28 -03:00
Kris Alcordo
39eb4aae04
client: Scroll Bar should not show unless necessary (#27)
Co-authored-by: Kris Alcordo <kalcordo@glimpselive.com>
2022-03-12 20:30:12 -08:00
Samuel
7f50654da4 fix: sign-in/up and add head component 2022-03-13 01:13:35 -03:00
Max Leiter
721d32fc35
Create CONTRIBUTING.md 2022-03-12 19:45:25 -08:00
Max Leiter
bbbbb19d26
client: move images to assets/, improve new post on mobile 2022-03-12 15:51:31 -08:00
Max Leiter
ace6de42bd
README: add setup instructions (#24) 2022-03-12 15:08:20 -08:00
Max Leiter
66c8a96e6a
Add issue templates 2022-03-12 15:07:07 -08:00
Max Leiter
64d37df5a3
Create LICENSE 2022-03-12 15:03:17 -08:00
Max Leiter
f9e9c6fe06
client: add downloading and viewing raw files (#21) 2022-03-11 18:48:40 -08:00
Max Leiter
606e38e192
client: switch to vs code color palette for syntax highlighting
this eliminates some WCAG contrast errors, especially in light mode.
2022-03-11 15:59:52 -08:00
Max Leiter
988b05d52d
client: add WELCOME_TITLE and WELCOME_MESSAGE as env vars 2022-03-11 15:57:42 -08:00
Max Leiter
a251d7f764
client: improved drag-and-drop styles for mobile 2022-03-11 15:49:45 -08:00
Max Leiter
6c0c45091f
README: add logo and mark file uploading as complete 2022-03-11 15:46:49 -08:00
Max Leiter
54adafa41d
client: drag and drop file uploading (#20) 2022-03-11 15:38:37 -08:00
299 changed files with 21149 additions and 8338 deletions

35
.env.default Normal file
View file

@ -0,0 +1,35 @@
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# Optional if you use Vercel (defaults to VERCEL_URL).
# Necessary in development unless you use the vercel CLI (`vc dev`)
DRIFT_URL=http://localhost:3000
# Optional: The first user becomes an admin. Defaults to false
ENABLE_ADMIN=false
# Required: Next auth secret is a required valid JWT secret. You can generate one with `openssl rand -hex 32`
NEXTAUTH_SECRET=7f8b8b5c5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5e5f5f5
# Required: but unnecessary if you use a supported host like Vercel
NEXTAUTH_URL=http://localhost:3000
# Optional: for locking your instance
REGISTRATION_PASSWORD=
# Optional: for if you want GitHub oauth. Currently incompatible with the registration password
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# Optional: if you want Keycloak oauth. Currently incompatible with the registration password
KEYCLOAK_ID=
KEYCLOAK_SECRET=
KEYCLOAK_ISSUER= # keycloak path including realm
KEYCLOAK_NAME=
# Optional: if you want to support credential auth (username/password, supports registration password)
# Defaults to true
CREDENTIAL_AUTH=true
# Optional:
WELCOME_CONTENT=
WELCOME_TITLE=

14
.eslintrc.json Normal file
View file

@ -0,0 +1,14 @@
{
"extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"],
"ignorePatterns": [
"node_modules/",
"__tests__/",
"coverage/",
".next/",
"public"
],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "error"
}
}

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: MaxLeiter

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View file

@ -0,0 +1,10 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: Feature request
assignees: ''
---

26
.github/workflows/server-CI.yaml vendored Normal file
View file

@ -0,0 +1,26 @@
name: Server CI
on:
push:
paths:
- 'server/**'
- '.github/workflows/**'
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: server
steps:
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install deps
run: yarn
- name: Run tests
run: yarn test

View file

@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
analyze
# testing
/coverage

8
.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"semi": false,
"trailingComma": "none",
"singleQuote": false,
"printWidth": 80,
"useTabs": true,
"plugins": ["prettier-plugin-tailwindcss"]
}

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"typescript.tsdk": "node_modules/.pnpm/typescript@4.9.4/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"dotenv.enableAutocloaking": false
}

16
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,16 @@
## Contributing
Thank you for your interest in Drift!
### I want to report a bug
Look at the open and closed issues to see if this was not already discussed before. If you can't see any, feel free to open a new issue.
If you think you discovered a security vulnerability, do not open a public issue on GitHub. Please email maxwell.leiter@gmail.com in the interest of responsible disclosure.
### I want to contribute to the code
Make sure to discuss your ideas with the community in an issue or on the IRC channel.
Take a look at the open issues labeled as help wanted or good first issue if you want to help without having a specific idea in mind.
Make sure that your PRs do not contain unnecessary commits or merge commits. Squash commits whenever possible.
Rebase (instead of merge) outdated PRs on the master branch.
Give extra care to your commit messages. Use the imperative present tense and follow Tim Pope's guidelines.

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Max Leiter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

152
README.md
View file

@ -1,29 +1,147 @@
# Drift
# <img src="src/public/assets/logo.png" height="32px" alt="" /> Drift
Drift is a self-hostable Gist clone. It's also a major work-in-progress, but is (almost, no database yet) completely functional.
> **Note:** This branch is where all work is being done to refactor to the Next.js 13 app directory and React Server Components.
You can try a demo at https://drift.maxleiter.com. The demo is built on master but has no database, so files and accounts can be wiped at any time.
Drift is a self-hostable Gist clone. It's in beta, but is completely functional.
You can try a demo at https://drift.lol. The demo is built on main but has no database, so files and accounts can be wiped at any time.
If you want to contribute, need support, or want to stay updated, you can join the IRC channel at #drift on irc.libera.chat or [reach me on twitter](https://twitter.com/Max_Leiter). If you don't have an IRC client yet, you can use a webclient [here](https://demo.thelounge.chat/#/connect?join=%23drift&nick=drift-user&realname=Drift%20User).
## Current status
Drit is a major work in progress. Below is a (rough) list of completed and envisioned features. If you want to help address any of them, please let me know regardless of your experience and I'll be happy to assist.
Drift is built with Next.js 13, React Server Components, [shadcn/ui](https://github.com/shadcn/ui), and [Prisma](https://prisma.io/).
- [x] creating and sharing private, public, unlisted posts
- [x] syntax highlighting (detected by file extension)
- [x] multiple files per post
- [ ] uploading files via drag-and-drop
<hr />
**Contents:**
- [Setup](#setup)
- [Development](#development)
- [Production](#production)
- [Environment variables](#environment-variables)
- [Running with pm2](#running-with-pm2)
- [Running with Docker](#running-with-docker)
- [Current status](#current-status)
## Setup
### Development
In the root directory, run `pnpm i`. If you need `pnpm`, you can download it [here](https://pnpm.io/installation).
You can run `pnpm dev` in `client` for file watching and live reloading.
To work with [prisma](prisma.io/), you can use `pnpm prisma` or `pnpm exec prisma` to interact with the database.
### Production
`pnpm build` will produce production code. `pnpm start` will start the Next.js server.
### Environment Variables
You can change these to your liking.
`.env`:
- `DRIFT_URL`: the URL of the drift instance.
- `DATABASE_URL`: the URL to connect to your postgres instance. For example, `postgresql://user:password@localhost:5432/drift`.
- `WELCOME_CONTENT`: a markdown string that's rendered on the home page
- `WELCOME_TITLE`: the file title for the post on the homepage.
- `ENABLE_ADMIN`: the first account created is an administrator account
- `REGISTRATION_PASSWORD`: the password required to register an account. If not set, no password is required.
- `NODE_ENV`: defaults to development, can be `production`
#### Auth environment variables
**Note:** Only credential auth currently supports the registration password, so if you want to secure registration, you must use only credential auth.
- `GITHUB_CLIENT_ID`: the client ID for GitHub OAuth.
- `GITHUB_CLIENT_SECRET`: the client secret for GitHub OAuth.
- `NEXTAUTH_URL`: the URL of the drift instance. Not required if hosting on Vercel.
- `CREDENTIAL_AUTH`: whether to allow username/password authentication. Defaults to `true`.
## Running with pm2
It's easy to start Drift using [pm2](https://pm2.keymetrics.io/).
First, add the `.env` file with your values (see the above section for the required options).
Then, use the following command to start the server:
- `pnpm build && pm2 start pnpm --name drift --interpreter bash -- start`
Refer to pm2's docs or `pm2 help` for more information.
## Running with Docker
## Running with systemd
_**NOTE:** We assume that you know how to enable user lingering if you don't want to use the systemd unit as root_
- As root
- Place the following systemd unit in ___/etc/systemd/system___ and name it _drift.service_
- Replace any occurrence of ___`$USERNAME`___ with the shell username of the user that will be running the Drift server
```
##########
# Drift Systemd Unit (Global)
##########
[Unit]
Description=Drift Server (Global)
After=default.target
[Service]
User=$USERNAME
Group=$USERNAME
Type=simple
WorkingDirectory=/home/$USERNAME/Drift
ExecStart=/usr/bin/pnpm start
Restart=on-failure
[Install]
WantedBy=default.target
```
- As a nomal user
- Place the following systemd unit inside ___/home/user/.config/systemd/user___ and name it _drift_user.service_
- Replace any occurrence of ___`$USERNAME`___ with the shell username of the user that will be running the Drift server
```
##########
# Drift Systemd Unit (User)
##########
[Unit]
Description=Drift Server (User)
After=default.target
[Service]
Type=simple
WorkingDirectory=/home/$USERNAME/Drift
ExecStart=/usr/bin/pnpm start
Restart=on-failure
[Install]
WantedBy=default.target
```
## Current status
Drift is a work in progress. Below is a (rough) list of completed and envisioned features. If you want to help address any of them, please let me know regardless of your experience and I'll be happy to assist.
- [x] Next.js 13 `app` directory
- [x] creating and sharing private, public, password-protected, and unlisted posts
- [x] syntax highlighting
- [x] expiring posts
- [x] responsive UI
- [x] user auth
- [ ] SSO via HTTP header (Issue: [#11](https://github.com/MaxLeiter/Drift/issues/11))
- [ ] downloading files (individually and entire posts)
- [ ] password protected posts
- [ ] sqlite database (should be very easy to set-up; the ORM is just currently set to memory for ease of development)
- [ ] non-node backend
- [ ] administrator account / settings
- [ ] docker-compose (PR: [#13](https://github.com/MaxLeiter/Drift/pull/13))
- [x] SSO via GitHub OAuth
- [x] downloading files (individually and entire posts)
- [x] password protected posts
- [x] postgres database
- [x] administrator account / settings
- [x] docker-compose (PRs: [#13](https://github.com/MaxLeiter/Drift/pull/13), [#75](https://github.com/MaxLeiter/Drift/pull/75))
- [ ] publish docker builds
- [ ] user settings
- [ ] works enough with JavaScript disabled
- [ ] documentation
- [ ] customizable homepage, so the demo can exist as-is but other instances can be built from the same source. Environment variable for the file contents?
- [ ] in-depth documentation
- [x] customizable homepage, so the demo can exist as-is but other instances can be built from the same source. Environment variable for the file contents?
- [ ] fleshed out API
- [ ] Swappable database backends
- [ ] More OAuth providers

View file

@ -1 +0,0 @@
node_modules/

View file

@ -1 +0,0 @@
API_URL=http://localhost:3000

View file

@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}

View file

@ -1,56 +0,0 @@
# Install dependencies only when needed
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# If using npm with a `package-lock.json` comment out above and use below instead
# COPY package.json package-lock.json ./
# RUN npm ci
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
RUN yarn build
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3001
ENV PORT 3001
CMD ["node", "server.js"]

View file

@ -1,34 +0,0 @@
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.tsx`. 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.ts`.
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.

View file

@ -1,12 +0,0 @@
import { Link as GeistLink, LinkProps } from "@geist-ui/core"
import { useRouter } from "next/router";
const Link = (props: LinkProps) => {
const { basePath } = useRouter();
const propHrefWithoutLeadingSlash = props.href && props.href.startsWith("/") ? props.href.substr(1) : props.href;
const href = basePath ? `${basePath}/${propHrefWithoutLeadingSlash}` : props.href;
(href)
return <GeistLink {...props} href={href} />
}
export default Link

View file

@ -1,21 +0,0 @@
.container {
padding: 2rem 2rem;
border-radius: var(--border-radius);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
}
.form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
}
.formGroup {
margin-bottom: 1rem;
}
.formHeader {
margin-bottom: 1rem;
}

View file

@ -1,100 +0,0 @@
import { FormEvent, useState } from 'react'
import { Button, Card, Input, Text } from '@geist-ui/core'
import styles from './auth.module.css'
import { useRouter } from 'next/router'
import Link from '../Link'
const Auth = ({ page }: { page: "signup" | "signin" }) => {
const router = useRouter();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const signingIn = page === 'signin'
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
const handleJson = (json: any) => {
if (json.error) {
setError(json.error.message)
} else {
localStorage.setItem('drift-token', json.token)
localStorage.setItem('drift-userid', json.userId)
router.push('/')
}
}
const reqOpts = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
}
e.preventDefault()
if (signingIn) {
try {
const resp = await fetch('/api/auth/signin', reqOpts)
const json = await resp.json()
handleJson(json)
} catch (err: any) {
setError(err.message || "Something went wrong")
}
} else {
try {
const resp = await fetch('/api/auth/signup', reqOpts)
const json = await resp.json()
handleJson(json)
} catch (err: any) {
setError(err.message || "Something went wrong")
}
}
}
return (
<div className={styles.container}>
<div className={styles.form}>
<div className={styles.formHeader}>
<h1>{signingIn ? 'Sign In' : 'Sign Up'}</h1>
</div>
<form onSubmit={handleSubmit}>
<Card>
<div className={styles.formGroup}>
<Input
htmlType="text"
id="username"
value={username}
onChange={(event) => setUsername(event.target.value)}
placeholder="Username"
required
label='Username'
/>
</div>
<div className={styles.formGroup}>
<Input
htmlType='password'
id="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
placeholder="Password"
required
label='Password'
/>
</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Button type="success" ghost htmlType="submit">{signingIn ? 'Sign In' : 'Sign Up'}</Button>
</div>
<div className={styles.formGroup}>
{signingIn && <Text>Don&apos;t have an account? <Link color href="/signup" >Sign up</Link></Text>}
{!signingIn && <Text>Already have an account? <Link color href="/signin" >Sign in</Link></Text>}
</div>
{error && <Text type='error'>{error}</Text>}
</Card>
</form>
</div>
</div >
)
}
export default Auth

View file

@ -1,31 +0,0 @@
.input {
background: #efefef;
}
.descriptionContainer {
display: flex;
flex-direction: column;
min-height: 400px;
overflow: scroll;
}
.fileNameContainer {
display: flex;
justify-content: space-between;
align-items: center;
height: 36px;
}
.fileNameContainer {
display: flex;
align-content: center;
}
.fileNameContainer > div {
/* Override geist-ui styling */
margin: 0 !important;
}
.textarea {
height: 100%;
}

View file

@ -1,116 +0,0 @@
import { Button, Card, Input, Spacer, Tabs, Textarea } from "@geist-ui/core"
import { ChangeEvent, memo, useMemo, useRef, useState } from "react"
import styles from './document.module.css'
import MarkdownPreview from '../preview'
import { Trash } from '@geist-ui/icons'
import FormattingIcons from "../formatting-icons"
import Skeleton from "react-loading-skeleton"
type Props = {
editable?: boolean
remove?: () => void
title?: string
content?: string
setTitle?: (title: string) => void
setContent?: (content: string) => void
initialTab?: "edit" | "preview"
skeleton?: boolean
}
const Document = ({ remove, editable, title, content, setTitle, setContent, initialTab = 'edit', skeleton }: Props) => {
const codeEditorRef = useRef<HTMLTextAreaElement>(null)
const [tab, setTab] = useState(initialTab)
const height = editable ? "500px" : '100%'
const handleTabChange = (newTab: string) => {
if (newTab === 'edit') {
codeEditorRef.current?.focus()
}
setTab(newTab as 'edit' | 'preview')
}
const getType = useMemo(() => {
if (!title) return
const pathParts = title.split(".")
const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : ""
return language
}, [title])
const removeFile = (remove?: () => void) => {
if (remove) {
if (content && content.trim().length > 0) {
const confirmed = window.confirm("Are you sure you want to remove this file?")
if (confirmed) {
remove()
}
} else {
remove()
}
}
}
if (skeleton) {
return <>
<Spacer height={1} />
<Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}>
<div className={styles.fileNameContainer}>
<Skeleton width={275} height={36} />
{editable && <Skeleton width={36} height={36} />}
</div>
<div className={styles.descriptionContainer}>
<div style={{ flexDirection: 'row', display: 'flex' }}><Skeleton width={125} height={36} /></div>
<Skeleton width={'100%'} height={350} />
</div >
</Card>
</>
}
return (
<>
<Spacer height={1} />
<Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}>
<div className={styles.fileNameContainer}>
<Input
placeholder="MyFile.md"
value={title}
onChange={(event: ChangeEvent<HTMLInputElement>) => setTitle ? setTitle(event.target.value) : null}
marginTop="var(--gap-double)"
size={1.2}
font={1.2}
label="Filename"
disabled={!editable}
width={"100%"}
/>
{remove && editable && <Button type="abort" ghost icon={<Trash />} auto height={'36px'} width={'36px'} onClick={() => removeFile(remove)} />}
</div>
<div className={styles.descriptionContainer}>
{tab === 'edit' && editable && <FormattingIcons setText={setContent} textareaRef={codeEditorRef} />}
<Tabs onChange={handleTabChange} initialValue={initialTab} hideDivider leftSpace={0}>
<Tabs.Item label={editable ? "Edit" : "Raw"} value="edit">
{/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */}
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Textarea
ref={codeEditorRef}
placeholder="Type some contents..."
value={content}
onChange={(event) => setContent ? setContent(event.target.value) : null}
width="100%"
disabled={!editable}
// TODO: Textarea should grow to fill parent if height == 100%
style={{ flex: 1, minHeight: 350 }}
resize="vertical"
className={styles.textarea}
/>
</div>
</Tabs.Item>
<Tabs.Item label="Preview" value="preview">
<MarkdownPreview height={height} content={content} type={getType} />
</Tabs.Item>
</Tabs>
</div >
</Card >
<Spacer height={1} />
</>
)
}
export default memo(Document)

View file

@ -1,139 +0,0 @@
import { ButtonGroup, Button } from "@geist-ui/core"
import { Bold, Italic, Link, Image as ImageIcon } from '@geist-ui/icons'
import { RefObject, useCallback, useMemo } from "react"
// TODO: clean up
const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTMLTextAreaElement>, setText?: (text: string) => void }) => {
// const { textBefore, textAfter, selectedText } = useMemo(() => {
// if (textareaRef && textareaRef.current) {
// const textarea = textareaRef.current
// const text = textareaRef.current.value
// const selectionStart = textarea.selectionStart
// const selectionEnd = textarea.selectionEnd
// const textBefore = text.substring(0, selectionStart)
// const textAfter = text.substring(selectionEnd)
// const selectedText = text.substring(selectionStart, selectionEnd)
// return { textBefore, textAfter, selectedText }
// }
// return { textBefore: '', textAfter: '' }
// }, [textareaRef,])
const handleBoldClick = useCallback((e) => {
if (textareaRef?.current && setText) {
const selectionStart = textareaRef.current.selectionStart
const selectionEnd = textareaRef.current.selectionEnd
const text = textareaRef.current.value
const before = text.substring(0, selectionStart)
const after = text.substring(selectionEnd)
const selectedText = text.substring(selectionStart, selectionEnd)
const newText = `${before}**${selectedText}**${after}`
setText(newText)
// TODO; fails because settext async
textareaRef.current.setSelectionRange(before.length + 2, before.length + 2 + selectedText.length)
}
}, [setText, textareaRef])
const handleItalicClick = useCallback((e) => {
if (textareaRef?.current && setText) {
const selectionStart = textareaRef.current.selectionStart
const selectionEnd = textareaRef.current.selectionEnd
const text = textareaRef.current.value
const before = text.substring(0, selectionStart)
const after = text.substring(selectionEnd)
const selectedText = text.substring(selectionStart, selectionEnd)
const newText = `${before}*${selectedText}*${after}`
setText(newText)
textareaRef.current.focus()
textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length)
}
}, [setText, textareaRef])
const handleLinkClick = useCallback((e) => {
if (textareaRef?.current && setText) {
const selectionStart = textareaRef.current.selectionStart
const selectionEnd = textareaRef.current.selectionEnd
const text = textareaRef.current.value
const before = text.substring(0, selectionStart)
const after = text.substring(selectionEnd)
const selectedText = text.substring(selectionStart, selectionEnd)
let formattedText = '';
if (selectedText.includes('http')) {
formattedText = `[](${selectedText})`
} else {
formattedText = `[${selectedText}](https://)`
}
const newText = `${before}${formattedText}${after}`
setText(newText)
textareaRef.current.focus()
textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length)
}
}, [setText, textareaRef])
const handleImageClick = useCallback((e) => {
if (textareaRef?.current && setText) {
const selectionStart = textareaRef.current.selectionStart
const selectionEnd = textareaRef.current.selectionEnd
const text = textareaRef.current.value
const before = text.substring(0, selectionStart)
const after = text.substring(selectionEnd)
const selectedText = text.substring(selectionStart, selectionEnd)
let formattedText = '';
if (selectedText.includes('http')) {
formattedText = `![](${selectedText})`
} else {
formattedText = `![${selectedText}](https://)`
}
const newText = `${before}${formattedText}${after}`
setText(newText)
textareaRef.current.focus()
textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length)
}
}, [setText, textareaRef])
const formattingActions = useMemo(() => [
{
icon: <Bold />,
name: 'bold',
action: handleBoldClick
},
{
icon: <Italic />,
name: 'italic',
action: handleItalicClick
},
// {
// icon: <Underline />,
// name: 'underline',
// action: handleUnderlineClick
// },
{
icon: <Link />,
name: 'hyperlink',
action: handleLinkClick
},
{
icon: <ImageIcon />,
name: 'image',
action: handleImageClick
}
], [handleBoldClick, handleImageClick, handleItalicClick, handleLinkClick])
return (
<div style={{ position: 'relative', zIndex: 1 }}>
<ButtonGroup style={{
position: 'absolute',
right: 0,
}}>
{formattingActions.map(({ icon, name, action }) => (
<Button auto scale={2 / 3} px={0.6} aria-label={name} key={name} icon={icon} onMouseDown={(e) => e.preventDefault()} onClick={action} />
))}
</ButtonGroup>
</div>
)
}
export default FormattingIcons

View file

@ -1,41 +0,0 @@
import React from 'react'
import MoonIcon from '@geist-ui/icons/moon'
import SunIcon from '@geist-ui/icons/sun'
import { Select } from '@geist-ui/core'
import { ThemeProps } from '../../pages/_app'
// import { useAllThemes, useTheme } from '@geist-ui/core'
import styles from './header.module.css'
const Controls = ({ changeTheme, theme }: ThemeProps) => {
const switchThemes = (type: string | string[]) => {
changeTheme()
if (typeof window === 'undefined' || !window.localStorage) return
window.localStorage.setItem('drift-theme', Array.isArray(type) ? type[0] : type)
}
return (
<div className={styles.wrapper}>
<Select
scale={0.5}
h="28px"
pure
onChange={switchThemes}
value={theme}
>
<Select.Option value="light">
<span className={styles.selectContent}>
<SunIcon size={14} /> Light
</span>
</Select.Option>
<Select.Option value="dark">
<span className={styles.selectContent}>
<MoonIcon size={14} /> Dark
</span>
</Select.Option>
</Select>
</div >
)
}
export default React.memo(Controls);

View file

@ -1,43 +0,0 @@
.tabs {
flex: 1 1;
padding: 0 var(--gap);
}
.mobile {
position: relative;
z-index: 2;
}
.controls {
display: none !important;
}
@media only screen and (max-width: 650px) {
.tabs {
display: none;
}
.controls {
display: block !important;
}
}
.controls button:active,
.controls button:focus,
.controls button:hover {
outline: 1px solid rgba(0, 0, 0, 0.2);
}
.wrapper {
display: flex;
align-items: center;
width: min-content;
}
.selectContent {
width: auto;
height: 18px;
display: flex;
justify-content: space-between;
align-items: center;
}

View file

@ -1,221 +0,0 @@
import { Page, ButtonGroup, Button, useBodyScroll, useMediaQuery, Tabs, Spacer } from "@geist-ui/core";
import { Github as GitHubIcon, UserPlus as SignUpIcon, User as SignInIcon, Home as HomeIcon, Menu as MenuIcon, Tool as SettingsIcon, UserX as SignoutIcon, PlusCircle as NewIcon, List as YourIcon, Moon, Sun } from "@geist-ui/icons";
import { DriftProps } from "../../pages/_app";
import { useEffect, useMemo, useState } from "react";
import styles from './header.module.css';
import { useRouter } from "next/router";
import useSignedIn from "../../lib/hooks/use-signed-in";
type Tab = {
name: string
icon: JSX.Element
condition?: boolean
value: string
onClick?: () => void
href?: string
}
const Header = ({ changeTheme, theme }: DriftProps) => {
const router = useRouter();
const [selectedTab, setSelectedTab] = useState<string>();
const [expanded, setExpanded] = useState<boolean>(false)
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
const isMobile = useMediaQuery('xs', { match: 'down' })
const { isLoading, isSignedIn, signout } = useSignedIn({ redirectIfNotAuthed: false })
const [pages, setPages] = useState<Tab[]>([])
useEffect(() => {
setBodyHidden(expanded)
}, [expanded, setBodyHidden])
useEffect(() => {
if (!isMobile) {
setExpanded(false)
}
}, [isMobile])
useEffect(() => {
const pageList: Tab[] = [
{
name: "Home",
href: "/",
icon: <HomeIcon />,
condition: true,
value: "home"
},
{
name: "New",
href: "/new",
icon: <NewIcon />,
condition: isSignedIn,
value: "new"
},
{
name: "Yours",
href: "/mine",
icon: <YourIcon />,
condition: isSignedIn,
value: "mine"
},
// {
// name: "Settings",
// href: "/settings",
// icon: <SettingsIcon />,
// condition: isSignedIn
// },
{
name: "Sign out",
onClick: () => {
if (typeof window !== 'undefined') {
localStorage.clear();
// // send token to API blacklist
// fetch('/api/auth/signout', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({
// token: localStorage.getItem("drift-token")
// })
// })
signout();
router.push("/signin");
}
},
href: "#signout",
icon: <SignoutIcon />,
condition: isSignedIn,
value: "signout"
},
{
name: "Sign in",
href: "/signin",
icon: <SignInIcon />,
condition: !isSignedIn,
value: "signin"
},
{
name: "Sign up",
href: "/signup",
icon: <SignUpIcon />,
condition: !isSignedIn,
value: "signup"
},
{
name: isMobile ? "GitHub" : "",
href: "https://github.com/maxleiter/drift",
icon: <GitHubIcon />,
condition: true,
value: "github"
},
{
name: isMobile ? "Change theme" : "",
onClick: function () {
if (typeof window !== 'undefined') {
changeTheme();
setSelectedTab(undefined);
}
},
icon: theme === 'light' ? <Moon /> : <Sun />,
condition: true,
value: "theme",
}
]
if (isLoading) {
return setPages([])
}
setPages(pageList.filter(page => page.condition))
}, [changeTheme, isLoading, isMobile, isSignedIn, router, signout, theme])
// useEffect(() => {
// setSelectedTab(pages.find((page) => {
// console.log(page.href, router.asPath)
// if (page.href && page.href === router.asPath) {
// return true
// }
// })?.href)
// }, [pages, router, router.pathname])
const onTabChange = (tab: string) => {
const match = pages.find(page => page.value === tab)
if (match?.onClick) {
match.onClick()
} else if (match?.href) {
router.push(`${match.href}`)
}
}
return (
<Page.Header height={'var(--page-nav-height)'} margin={0} paddingBottom={0} paddingTop={"var(--gap)"}>
<div className={styles.tabs}>
<Tabs
value={selectedTab}
leftSpace={0}
align="center"
hideDivider
hideBorder
onChange={onTabChange}>
{!isLoading && pages.map((tab) => {
return <Tabs.Item
font="14px"
label={<>{tab.icon} {tab.name}</>}
value={tab.value}
key={`${tab.value}`}
/>
})}
</Tabs>
</div>
<div className={styles.controls}>
<Button
auto
type="abort"
onClick={() => setExpanded(!expanded)}
>
<Spacer height={5 / 6} width={0} />
<MenuIcon />
</Button>
</div>
{isMobile && expanded && (<div className={styles.mobile}>
<ButtonGroup vertical>
{pages.map((tab, index) => {
return <Button
key={`${tab.name}-${index}`}
onClick={() => onTabChange(tab.value)}
icon={tab.icon}
>
{tab.name}
</Button>
})}
</ButtonGroup>
</div>)}
</Page.Header >
)
}
export default Header
// {/* {/* <ButtonGroup>
// <Button onClick={() => {
// }}><Link href="/signin">Sign out</Link></Button>
// <Button>
// <Link href="/mine">
// Yours
// </Link>
// </Button>
// <Button>
// {/* TODO: Link outside Button, but seems to break ButtonGroup */}
// <Link href="/new">
// New
// </Link>
// </Button >
// <Button onClick={() => changeTheme()}>
// <ShiftBy y={6}>{theme.type === 'light' ? <Moon /> : <Sun />}</ShiftBy>
// </Button>
// </ButtonGroup > * /}

View file

@ -1,16 +0,0 @@
import useSWR from "swr"
import PostList from "../post-list"
const fetcher = (url: string) => fetch(url, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem("drift-token")}`
},
}).then(r => r.json())
const MyPosts = () => {
const { data, error } = useSWR('/api/users/mine', fetcher)
return <PostList posts={data} error={error} />
}
export default MyPosts

View file

@ -1,40 +0,0 @@
import { Text } from "@geist-ui/core"
import NextLink from "next/link"
import Link from '../Link'
import styles from './post-list.module.css'
import ListItemSkeleton from "./list-item-skeleton"
import ListItem from "./list-item"
type Props = {
posts: any
error: any
}
const PostList = ({ posts, error }: Props) => {
return (
<div className={styles.container}>
{error && <Text type='error'>Failed to load.</Text>}
{!posts && <ul>
<li>
<ListItemSkeleton />
</li>
<li>
<ListItemSkeleton />
</li>
</ul>}
{posts?.length === 0 && <Text>You have no posts. Create one <NextLink passHref={true} href="/new"><Link color>here</Link></NextLink>.</Text>}
{
posts?.length > 0 && <div>
<ul>
{posts.map((post: any) => {
return <ListItem post={post} key={post.id} />
})}
</ul>
</div>
}
</div >
)
}
export default PostList

View file

@ -1,19 +0,0 @@
import { Card, Spacer, Grid, Divider } from "@geist-ui/core";
import Skeleton from "react-loading-skeleton";
const ListItemSkeleton = () => (<Card>
<Spacer height={1 / 2} />
<Grid.Container justify={'space-between'} marginBottom={1 / 2}>
<Grid xs={8} paddingLeft={1 / 2}><Skeleton width={150} /></Grid>
<Grid xs={7}><Skeleton width={100} /></Grid>
<Grid xs={4}><Skeleton width={70} /></Grid>
</Grid.Container>
<Divider h="1px" my={0} />
<Card.Content >
<Skeleton width={200} />
</Card.Content>
</Card>)
export default ListItemSkeleton

View file

@ -1,58 +0,0 @@
import { Card, Spacer, Grid, Divider, Link, Text, Input, Tooltip } from "@geist-ui/core"
import NextLink from "next/link"
import { useEffect, useMemo, useState } from "react"
import timeAgo from "../../lib/time-ago"
import ShiftBy from "../shift-by"
import VisibilityBadge from "../visibility-badge"
const FilenameInput = ({ title }: { title: string }) => <Input
value={title}
marginTop="var(--gap-double)"
size={1.2}
font={1.2}
label="Filename"
readOnly
width={"100%"}
/>
const ListItem = ({ post }: { post: any }) => {
const createdDate = useMemo(() => new Date(post.createdAt), [post.createdAt])
const [time, setTimeAgo] = useState(timeAgo(createdDate))
useEffect(() => {
const interval = setInterval(() => {
setTimeAgo(timeAgo(createdDate))
}, 10000)
return () => clearInterval(interval)
}, [createdDate])
const formattedTime = `${createdDate.toLocaleDateString()} ${createdDate.toLocaleTimeString()}`
return (<li key={post.id}>
<Card style={{ overflowY: 'scroll' }}>
<Spacer height={1 / 2} />
<Grid.Container justify={'space-between'}>
<Grid xs={8}>
<Text h3 paddingLeft={1 / 2}>
<NextLink passHref={true} href={`/post/${post.id}`}>
<Link color>{post.title}
<ShiftBy y={-1}><VisibilityBadge visibility={post.visibility} /></ShiftBy>
</Link>
</NextLink>
</Text></Grid>
<Grid xs={7}><Text type="secondary" h5><Tooltip text={formattedTime}>{time}</Tooltip></Text></Grid>
<Grid xs={4}><Text type="secondary" h5>{post.files.length === 1 ? "1 file" : `${post.files.length} files`}</Text></Grid>
</Grid.Container>
<Divider h="1px" my={0} />
<Card.Content >
{post.files.map((file: any) => {
return <FilenameInput key={file.id} title={file.title} />
})}
</Card.Content>
</Card>
</li>)
}
export default ListItem

View file

@ -1,26 +0,0 @@
.container ul {
list-style: none;
padding: 0;
margin: 0;
}
.container ul li {
padding: 0.5rem 0;
}
.container ul li::before {
content: "";
padding: 0;
margin: 0;
}
.postHeader {
display: flex;
justify-content: space-between;
padding: var(--gap);
align-items: center;
position: sticky;
top: 0;
z-index: 1;
background: inherit;
}

View file

@ -1,109 +0,0 @@
import { Button, ButtonDropdown, useToasts } from '@geist-ui/core'
import { useRouter } from 'next/router';
import { useCallback, useState } from 'react'
import generateUUID from '../../lib/generate-uuid';
import Document from '../document';
import styles from './post.module.css'
import Title from './title';
type Document = {
title: string
content: string
id: string
}
const Post = () => {
const { setToast } = useToasts()
const router = useRouter();
const [title, setTitle] = useState<string>()
const [docs, setDocs] = useState<Document[]>([{
title: '',
content: '',
id: generateUUID()
}])
const [isSubmitting, setSubmitting] = useState(false)
const remove = (id: string) => {
setDocs(docs.filter((doc) => doc.id !== id))
}
const onSubmit = async (visibility: string) => {
setSubmitting(true)
const response = await fetch('/api/posts/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem("drift-token")}`
},
body: JSON.stringify({
title,
files: docs,
visibility,
userId: localStorage.getItem("drift-userid"),
})
})
const json = await response.json()
setSubmitting(false)
if (json.id)
router.push(`/post/${json.id}`)
else {
setToast({ text: json.error.message, type: "error" })
}
}
const updateTitle = useCallback((title: string, id: string) => {
setDocs(docs.map((doc) => doc.id === id ? { ...doc, title } : doc))
}, [docs])
const updateContent = useCallback((content: string, id: string) => {
setDocs(docs.map((doc) => doc.id === id ? { ...doc, content } : doc))
}, [docs])
return (
<div>
<Title title={title} setTitle={setTitle} />
{
docs.map(({ id }) => {
const doc = docs.find((doc) => doc.id === id)
return (
<Document
remove={() => remove(id)}
key={id}
editable={true}
setContent={(content) => updateContent(content, id)}
setTitle={(title) => updateTitle(title, id)}
content={doc?.content}
title={doc?.title}
/>
)
})
}
<div className={styles.buttons}>
<Button
className={styles.button}
onClick={() => {
setDocs([...docs, {
title: '',
content: '',
id: generateUUID()
}])
}}
style={{ flex: .5, lineHeight: '40px' }}
type="default"
>
Add a File
</Button>
<ButtonDropdown loading={isSubmitting} type="success">
<ButtonDropdown.Item main onClick={() => onSubmit('private')}>Create Private</ButtonDropdown.Item>
<ButtonDropdown.Item onClick={() => onSubmit('public')} >Create Public</ButtonDropdown.Item>
<ButtonDropdown.Item onClick={() => onSubmit('unlisted')} >Create Unlisted</ButtonDropdown.Item>
</ButtonDropdown>
</div>
</div >
)
}
export default Post

View file

@ -1,21 +0,0 @@
.buttons {
position: relative;
display: flex;
justify-content: space-between;
width: 100%;
margin-top: var(--gap-double);
}
.title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
@media screen and (max-width: 650px) {
.title {
align-items: flex-start;
flex-direction: column;
}
}

View file

@ -1,39 +0,0 @@
import { Text, Input } from '@geist-ui/core'
import { memo } from 'react'
import ShiftBy from '../../shift-by'
import styles from '../post.module.css'
const titlePlaceholders = [
"How to...",
"Status update for ...",
"My new project",
"My new idea",
"Let's talk about...",
"What's up with ...",
"I'm thinking about ...",
]
type props = {
setTitle: (title: string) => void
title?: string
}
const Title = ({ setTitle, title }: props) => {
return (<div className={styles.title}>
<Text h1 width={"150px"} className={styles.drift}>Drift</Text>
<ShiftBy y={-3}>
<Input
placeholder={titlePlaceholders[Math.floor(Math.random() * titlePlaceholders.length)]}
value={title || ""}
onChange={(event) => setTitle(event.target.value)}
height={"55px"}
font={1.5}
label="Post title"
marginLeft={'var(--gap)'}
style={{ width: "100%" }}
/>
</ShiftBy>
</div>)
}
export default memo(Title)

View file

@ -1,28 +0,0 @@
import { memo, useEffect, useState } from "react"
import ReactMarkdownPreview from "./react-markdown-preview"
type Props = {
content?: string
height?: number | string
// file extensions we can highlight
type?: string
}
const MarkdownPreview = ({ content = '', height = 500, type = 'markdown' }: Props) => {
const [contentToRender, setContent] = useState(content)
useEffect(() => {
// 'm' so it doesn't flash code when you change the type to md
const renderAsMarkdown = ['m', 'markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', '']
if (!renderAsMarkdown.includes(type)) {
setContent(`~~~${type}
${content}
~~~
`)
} else {
setContent(content)
}
}, [type, content])
return (<ReactMarkdownPreview height={height} content={contentToRender} />)
}
export default memo(MarkdownPreview)

View file

@ -1,60 +0,0 @@
.markdownPreview pre {
border-radius: 3px;
font-family: "Courier New", Courier, monospace;
font-size: 14px;
line-height: 1.42857143;
margin: 0;
padding: 10px;
white-space: pre-wrap;
word-wrap: break-word;
}
.markdownPreview h1,
.markdownPreview h2,
.markdownPreview h3,
.markdownPreview h4,
.markdownPreview h5,
.markdownPreview h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
.markdownPreview h1 {
font-size: 2rem;
}
.markdownPreview h2 {
font-size: 1.5rem;
}
.markdownPreview h3 {
font-size: 1.25rem;
}
.markdownPreview h4 {
font-size: 1rem;
}
.markdownPreview h5 {
font-size: 0.875rem;
}
.markdownPreview h6 {
font-size: 0.75rem;
}
.markdownPreview ul {
list-style: inside;
}
.markdownPreview ul li::before {
content: "";
}
.markdownPreview ul ul {
list-style: circle;
}
.markdownPreview ul ul li {
margin-left: var(--gap);
}

View file

@ -1,53 +0,0 @@
import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm"
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
// @ts-ignore because of no types in remark-a11y-emoji
import a11yEmoji from '@fec/remark-a11y-emoji';
import styles from './preview.module.css'
import { duotoneDark, duotoneLight } from 'react-syntax-highlighter/dist/cjs/styles/prism'
import useSharedState from "../../lib/hooks/use-shared-state";
type Props = {
content: string | undefined
height: number | string
}
const ReactMarkdownPreview = ({ content, height }: Props) => {
const [themeType] = useSharedState<string>('theme')
return (<div style={{ height }}>
<ReactMarkdown className={styles.markdownPreview} remarkPlugins={[remarkGfm, a11yEmoji]}
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
return !inline && match ? (
<SyntaxHighlighter
lineNumberStyle={{
minWidth: "2.25rem"
}}
customStyle={{
padding: 0,
margin: 0,
background: 'transparent'
}}
codeTagProps={{
style: { background: 'transparent' }
}}
style={themeType === 'dark' ? duotoneDark : duotoneLight}
showLineNumbers={true}
language={match[1]}
PreTag="div"
{...props}
>{String(children).replace(/\n$/, '')}</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}}>
{content || ""}
</ReactMarkdown></div>)
}
export default ReactMarkdownPreview

View file

@ -1,20 +0,0 @@
// https://www.joshwcomeau.com/snippets/react-components/shift-by/
type Props = {
x?: number
y?: number
children: React.ReactNode
}
function ShiftBy({ x = 0, y = 0, children }: Props) {
return (
<div
style={{
transform: `translate(${x}px, ${y}px)`,
display: 'inline-block'
}}
>
{children}
</div>
)
}
export default ShiftBy

View file

@ -1,24 +0,0 @@
import { Badge } from "@geist-ui/core"
type Visibility = "unlisted" | "private" | "public"
type Props = {
visibility: Visibility
}
const VisibilityBadge = ({ visibility }: Props) => {
const getBadgeType = () => {
switch (visibility) {
case "public":
return "success"
case "private":
return "warning"
case "unlisted":
return "default"
}
}
return (<Badge marginLeft={'var(--gap)'} type={getBadgeType()}>{visibility}</Badge>)
}
export default VisibilityBadge

View file

@ -1,30 +0,0 @@
export default function generateUUID() {
if (typeof crypto === 'object') {
if (typeof crypto.randomUUID === 'function') {
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID
return crypto.randomUUID();
}
if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {
// https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid
const callback = (c: string) => {
const num = Number(c);
return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16);
};
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback);
}
}
let timestamp = new Date().getTime();
let perforNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
let random = Math.random() * 16;
if (timestamp > 0) {
random = (timestamp + random) % 16 | 0;
timestamp = Math.floor(timestamp / 16);
} else {
random = (perforNow + random) % 16 | 0;
perforNow = Math.floor(perforNow / 16);
}
return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16);
});
};

View file

@ -1,11 +0,0 @@
import useSWR from "swr"
// https://2020.paco.me/blog/shared-hook-state-with-swr
const useSharedState = <T>(key: string, initial?: T) => {
const { data: state, mutate: setState } = useSWR(key, {
fallbackData: initial
})
return [state, setState] as const
}
export default useSharedState

View file

@ -1,44 +0,0 @@
import { useRouter } from "next/router";
import { useCallback, useEffect } from "react"
import useSharedState from "./use-shared-state";
const useSignedIn = ({ redirectIfNotAuthed = false }: { redirectIfNotAuthed?: boolean }) => {
const [isSignedIn, setSignedIn] = useSharedState('isSignedIn', false)
const [isLoading, setLoading] = useSharedState('isLoading', true)
const signout = useCallback(() => setSignedIn(false), [setSignedIn])
const router = useRouter();
if (redirectIfNotAuthed && !isLoading && isSignedIn === false) {
router.push('/signin')
}
useEffect(() => {
async function checkToken() {
const token = localStorage.getItem('drift-token')
if (token) {
const response = await fetch('/api/auth/verify-token', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
setSignedIn(true)
}
}
setLoading(false)
}
setLoading(true)
checkToken()
const interval = setInterval(() => {
checkToken()
}, 10000);
return () => clearInterval(interval);
}, [setLoading, setSignedIn])
return { isSignedIn, isLoading, signout }
}
export default useSignedIn

View file

@ -1,41 +0,0 @@
// Modified from https://gist.github.com/IbeVanmeenen/4e3e58820c9168806e57530563612886
// which is based on https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
const epochs = [
['year', 31536000],
['month', 2592000],
['day', 86400],
['hour', 3600],
['minute', 60],
['second', 1]
] as const;
// Get duration
const getDuration = (timeAgoInSeconds: number) => {
for (let [name, seconds] of epochs) {
const interval = Math.floor(timeAgoInSeconds / seconds);
if (interval >= 1) {
return {
interval: interval,
epoch: name
};
}
}
return {
interval: 0,
epoch: 'second'
}
};
// Calculate
const timeAgo = (date: Date) => {
const timeAgoInSeconds = Math.floor((new Date().getTime() - new Date(date).getTime()) / 1000);
const { interval, epoch } = getDuration(timeAgoInSeconds);
const suffix = interval === 1 ? '' : 's';
return `${interval} ${epoch}${suffix} ago`;
};
export default timeAgo

View file

@ -1,20 +0,0 @@
const dotenv = require("dotenv");
dotenv.config();
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
outputStandalone: true,
},
async rewrites() {
return [
{
source: "/api/:path*",
destination: `${process.env.API_URL}/:path*`,
},
];
},
};
module.exports = nextConfig;

View file

@ -1,40 +0,0 @@
{
"name": "drift",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --port 3001",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@fec/remark-a11y-emoji": "^3.1.0",
"@geist-ui/core": "^2.3.5",
"@geist-ui/icons": "^1.0.1",
"comlink": "^4.3.1",
"dotenv": "^16.0.0",
"next": "12.1.0",
"prismjs": "^1.27.0",
"react": "17.0.2",
"react-debounce-render": "^8.0.2",
"react-dom": "17.0.2",
"react-loading-skeleton": "^3.0.3",
"react-markdown": "^8.0.0",
"react-syntax-highlighter": "^15.4.5",
"react-syntax-highlighter-virtualized-renderer": "^1.1.0",
"rehype-katex": "^6.0.2",
"rehype-stringify": "^9.0.3",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"swr": "^1.2.2"
},
"devDependencies": {
"@types/node": "17.0.21",
"@types/react": "17.0.39",
"@types/react-syntax-highlighter": "^13.5.2",
"eslint": "8.10.0",
"eslint-config-next": "12.1.0",
"typescript": "4.6.2"
}
}

View file

@ -1,73 +0,0 @@
import '../styles/globals.css'
import { GeistProvider, CssBaseline, useTheme } from '@geist-ui/core'
import { useEffect, useMemo, useState } from 'react'
import type { AppProps as NextAppProps } from "next/app";
import useSharedState from '../lib/hooks/use-shared-state';
import 'react-loading-skeleton/dist/skeleton.css'
import { SkeletonTheme } from 'react-loading-skeleton';
import Head from 'next/head';
export type ThemeProps = {
theme: "light" | "dark" | string,
changeTheme: () => void
}
type AppProps<P = any> = {
pageProps: P;
} & Omit<NextAppProps<P>, "pageProps">;
export type DriftProps = ThemeProps
function MyApp({ Component, pageProps }: AppProps<ThemeProps>) {
const [themeType, setThemeType] = useSharedState<string>('theme', 'light')
const theme = useTheme();
useEffect(() => {
if (typeof window === 'undefined' || !window.localStorage) return
const storedTheme = window.localStorage.getItem('drift-theme')
if (storedTheme) setThemeType(storedTheme)
// TODO: useReducer?
}, [setThemeType, themeType])
const changeTheme = () => {
const newTheme = themeType === 'dark' ? 'light' : 'dark'
localStorage.setItem('drift-theme', newTheme)
setThemeType(last => (last === 'dark' ? 'light' : 'dark'))
}
const skeletonBaseColor = useMemo(() => {
if (themeType === 'dark') return '#333'
return '#eee'
}, [themeType])
const skeletonHighlightColor = useMemo(() => {
if (themeType === 'dark') return '#555'
return '#ddd'
}, [themeType])
return (
<>
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="apple-mobile-web-app-title" content="Drift" />
<meta name="application-name" content="Drift" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
</Head>
<GeistProvider themeType={themeType} >
<SkeletonTheme baseColor={skeletonBaseColor} highlightColor={skeletonHighlightColor}>
<CssBaseline />
<Component {...pageProps} theme={themeType || 'light'} changeTheme={changeTheme} />
</SkeletonTheme>
</GeistProvider>
</>
)
}
export default MyApp

View file

@ -1,31 +0,0 @@
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'
import { CssBaseline } from '@geist-ui/core'
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx)
const styles = CssBaseline.flush()
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{styles}
</>
)
}
}
render() {
return (<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>)
}
}
export default MyDocument

View file

@ -1,66 +0,0 @@
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import { Page, Spacer, Text } from '@geist-ui/core'
import Header from '../components/header'
import { ThemeProps } from './_app'
import Document from '../components/document'
import Image from 'next/image'
import ShiftBy from '../components/shift-by'
export function getStaticProps() {
const introDoc = `### Drift is a self-hostable clone of GitHub Gist.
#### It is a simple way to share code and text snippets with your friends, with support for the following:
- Render GitHub Extended Markdown (including images)
- User authentication
- Private, public, and secret posts
If you want to signup, you can join at [/signup](/signup) as long as you have a passcode provided by the administrator (which you don't need for this demo).
**This demo is on a memory-only database, so accounts and pastes can be deleted at any time.**
You can find the source code on [GitHub](https://github.com/MaxLeiter/drift).
Drift was inspired by [this tweet](https://twitter.com/emilyst/status/1499858264346935297):
> What is the absolute closest thing to GitHub Gist that can be self-hosted?
In terms of design and functionality. Hosts images and markdown, rendered. Creates links that can be private or public. Uses/requires registration.
I have looked at dozens of pastebin-like things.
`
return {
props: {
introContent: introDoc,
}
}
}
type Props = ThemeProps & {
introContent: string
}
const Home = ({ theme, changeTheme, introContent }: Props) => {
return (
<Page className={styles.container} width="100%">
<Head>
<title>Drift</title>
<meta name="description" content="A self-hostable clone of GitHub Gist" />
</Head>
<Page.Header>
<Header theme={theme} changeTheme={changeTheme} />
</Page.Header>
<Page.Content width={"var(--main-content-width)"} margin="auto" paddingTop={"var(--gap)"}>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<ShiftBy y={-2}><Image src={'/assets/logo-optimized.svg'} width={'48px'} height={'48px'} alt="" /></ShiftBy>
<Spacer />
<Text style={{ display: 'inline' }} h1> Welcome to Drift</Text>
</div>
<Document
editable={false}
content={introContent}
title={`Welcome to Drift.md`}
initialTab={`preview`}
/>
</Page.Content>
</Page >
)
}
export default Home

View file

@ -1,25 +0,0 @@
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import { Page } from '@geist-ui/core'
import Header from '../components/header'
import MyPosts from '../components/my-posts'
const Home = ({ theme, changeTheme }: { theme: "light" | "dark", changeTheme: () => void }) => {
return (
<Page className={styles.container} width="100%">
<Head>
<title>Drift</title>
<meta name="description" content="A self-hostable clone of GitHub Gist" />
</Head>
<Page.Header>
<Header theme={theme} changeTheme={changeTheme} />
</Page.Header>
<Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}>
<MyPosts />
</Page.Content>
</Page >
)
}
export default Home

View file

@ -1,33 +0,0 @@
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import HomeComponent from '../components/post'
import { Page } from '@geist-ui/core'
import useSignedIn from '../lib/hooks/use-signed-in'
import Header from '../components/header'
import { ThemeProps } from './_app'
import { useRouter } from 'next/router'
const Home = ({ theme, changeTheme }: ThemeProps) => {
const router = useRouter()
const { isSignedIn, isLoading } = useSignedIn({ redirectIfNotAuthed: true })
if (!isSignedIn && !isLoading) {
router.push("/signin")
}
return (
<Page className={styles.container} width="100%">
<Head>
<title>Drift</title>
</Head>
<Page.Header>
<Header theme={theme} changeTheme={changeTheme} />
</Page.Header>
<Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}>
{isSignedIn && <HomeComponent />}
</Page.Content>
</Page >
)
}
export default Home

View file

@ -1,82 +0,0 @@
import { Page, Text } from "@geist-ui/core";
import Skeleton from 'react-loading-skeleton';
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import Document from '../../components/document'
import Header from "../../components/header";
import VisibilityBadge from "../../components/visibility-badge";
import { ThemeProps } from "../_app";
import Head from "next/head";
const Post = ({ theme, changeTheme }: ThemeProps) => {
const [post, setPost] = useState<any>()
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string>()
const router = useRouter();
useEffect(() => {
async function fetchPost() {
setIsLoading(true);
if (router.query.id) {
const post = await fetch(`/api/posts/${router.query.id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${localStorage.getItem("drift-token")}`
}
})
if (post.ok) {
const res = await post.json()
if (res)
setPost(res)
else
setError("Post not found")
} else {
if (post.status.toString().startsWith("4")) {
router.push("/signin")
} else {
setError(post.statusText)
}
}
setIsLoading(false)
}
}
fetchPost()
}, [router, router.query.id])
return (
<Page width={"100%"}>
<Head>
{isLoading && <title>loading - Drift</title>}
{!isLoading && <title>{post.title} - Drift</title>}
{!isLoading && post.visibility !== 'private' && <meta name="description" content={post.description} />}
</Head>
<Page.Header>
<Header theme={theme} changeTheme={changeTheme} />
</Page.Header>
<Page.Content width={"var(--main-content-width)"} margin="auto">
{error && <Text type="error">{error}</Text>}
{/* {!error && (isLoading || !post?.files) && <Loading />} */}
{!error && isLoading && <><Text h2><Skeleton width={400} /></Text>
<Document skeleton={true} />
</>}
{!isLoading && post && <><Text h2>{post.title} <VisibilityBadge visibility={post.visibility} /></Text>
{post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
<Document
key={id}
content={content}
title={title}
editable={false}
initialTab={'preview'}
/>
))}
</>}
</Page.Content>
</Page >
)
}
export default Post

View file

@ -1,22 +0,0 @@
import { Page } from "@geist-ui/core";
import Head from 'next/head'
import Auth from "../components/auth";
import Header from "../components/header";
import { ThemeProps } from "./_app";
const SignIn = ({ theme, changeTheme }: ThemeProps) => (
<Page width={"100%"}>
<Head>
<title>Drift - Sign In</title>
<meta name="description" content="A self-hostable clone of GitHub Gist" />
</Head>
<Page.Header>
<Header theme={theme} changeTheme={changeTheme} />
</Page.Header>
<Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="auto">
<Auth page="signin" />
</Page.Content>
</Page>
)
export default SignIn

View file

@ -1,22 +0,0 @@
import { Page } from "@geist-ui/core";
import Head from "next/head";
import Auth from "../components/auth";
import Header from "../components/header";
import { ThemeProps } from "./_app";
const SignUp = ({ theme, changeTheme }: ThemeProps) => (
<Page width="100%">
<Head>
<title>Drift - Sign Up</title>
<meta name="description" content="A self-hostable clone of GitHub Gist" />
</Head>
<Page.Header>
<Header theme={theme} changeTheme={changeTheme} />
</Page.Header>
<Page.Content width={"var(--main-content-width)"} paddingTop={"var(--gap)"} margin="auto">
<Auth page="signup" />
</Page.Content>
</Page>
)
export default SignUp

View file

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="72.000008"
height="72"
viewBox="0 0 19.05 19.05"
version="1.1"
id="svg5"
inkscape:export-filename="/home/reese/git/github.com/maxleiter/drift/logo.png"
inkscape:export-xdpi="682.66669"
inkscape:export-ydpi="682.66669"
inkscape:version="1.1.2 (1:1.1+202202050950+0a00cf5339)"
sodipodi:docname="logo.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:document-units="px"
showgrid="false"
showguides="false"
inkscape:zoom="13.877295"
inkscape:cx="10.448722"
inkscape:cy="34.444753"
inkscape:current-layer="g3632"
units="px"
viewbox-width="19.05" />
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7860">
<circle
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.93688;stroke-linecap:round"
id="circle7862"
cx="115.27311"
cy="135.3275"
r="9.1405506" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath7864">
<circle
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.93688;stroke-linecap:round"
id="circle7866"
cx="115.27311"
cy="135.3275"
r="9.1405506" />
</clipPath>
</defs>
<g
inkscape:label="source strokes"
inkscape:groupmode="layer"
id="layer1"
style="display:none"
transform="translate(-106.13256,-126.18696)">
<path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 114.7741,133.0871 c 0,0 2.24373,3.38322 0.005,7.06735 -2.23896,3.68413 -8.84476,5.87171 -8.84476,5.87171"
id="path1824"
sodipodi:nodetypes="csc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 99.71221,140.61603 c 0,0 6.55112,-0.26544 10.1251,-2.27285 3.57398,-2.00741 4.93679,-5.25608 4.93679,-5.25608"
id="path857"
sodipodi:nodetypes="czc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 114.7741,133.0871 c 0,0 3.22515,3.50294 1.78507,7.47454 -1.44009,3.97159 -7.66948,7.78507 -7.66948,7.78507"
id="path949"
sodipodi:nodetypes="czc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 114.7741,133.0871 c 0,0 6.66681,-0.12736 17.37373,8.90799"
id="path1345"
sodipodi:nodetypes="cc" />
</g>
<g
inkscape:label="Layer 1 copy"
inkscape:groupmode="layer"
id="g3632"
transform="translate(-106.13256,-126.18696)">
<rect
style="display:inline;fill:#1b1b1b;fill-opacity:1;stroke:none;stroke-width:2.81834;stroke-linecap:round"
id="rect6284"
width="18.28112"
height="18.28112"
x="106.13255"
y="126.18695"
clip-path="url(#clipPath7864)"
transform="matrix(1.0420598,0,0,1.0420598,-4.4639102,-5.3073932)" />
<g
id="g937"
inkscape:label="drift"
clip-path="url(#clipPath7860)"
mask="none"
style="display:inline;stroke-width:0.959638"
transform="matrix(1.0420598,0,0,1.0420598,-4.4639102,-5.3073932)">
<path
id="path935"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.253904px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 132.14783,141.99509 c -10.70692,-9.03535 -17.37373,-8.90799 -17.37373,-8.90799 0,0 2.38807,3.48286 0.94799,7.45446 -1.44009,3.97159 -7.66636,7.74668 -7.66636,7.74668 z"
sodipodi:nodetypes="csccc" />
<path
id="path931"
style="fill:#e7e7e7;fill-opacity:1;stroke:none;stroke-width:0.253904px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 108.88969,148.34671 c 0,0 6.22939,-3.81348 7.66948,-7.78507 1.44008,-3.9716 -1.78507,-7.47454 -1.78507,-7.47454 0,0 1.22037,3.09102 -1.01836,6.77515 -2.23896,3.68413 -8.92258,4.9787 -8.92258,4.9787 z"
sodipodi:nodetypes="cscccc" />
<path
id="path933"
style="fill:#c6c6c6;fill-opacity:1;stroke:none;stroke-width:0.253904px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 105.93434,146.02616 c 0,0 6.6058,-2.18758 8.84476,-5.87171 2.23873,-3.68413 -0.005,-7.06735 -0.005,-7.06735 0,0 -1.36281,3.24867 -4.93679,5.25608 -3.57398,2.00741 -10.1251,2.27285 -10.1251,2.27285 z"
sodipodi:nodetypes="csccc" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -1,28 +0,0 @@
.main {
min-height: 100vh;
flex: 1;
display: flex;
flex-direction: column;
margin: 0 auto;
width: var(--main-content-width);
}
.container {
width: 100% !important;
}
@media screen and (max-width: 768px) {
.container {
width: 100%;
margin: 0 auto !important;
padding: 0;
}
.container h1 {
font-size: 2rem;
}
.main {
width: 100%;
}
}

View file

@ -1,32 +0,0 @@
:root {
--main-content-width: 800px;
--page-nav-height: 60px;
--gap: 8px;
--gap-half: calc(var(--gap) / 2);
--gap-double: calc(var(--gap) * 2);
--border-radius: 4px;
--font-size: 16px;
}
@media screen and (max-width: 768px) {
:root {
--main-content-width: 100%;
}
}
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}

View file

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

File diff suppressed because it is too large Load diff

16
components.json Normal file
View file

@ -0,0 +1,16 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@components",
"utils": "@utils"
}
}

34
docker-compose.yml Normal file
View file

@ -0,0 +1,34 @@
services:
server:
build:
context: ./server
args:
- NODE_ENV=production
container_name: server
restart: unless-stopped
user: 1000:1000
environment:
- PORT
- JWT_SECRET=jwt_secret # change_me! # use `openssl rand -hex 32` to generate a strong secret
- SECRET_KEY=secret # change me!
- MEMORY_DB
- REGISTRATION_PASSWORD
- WELCOME_CONTENT
- WELCOME_TITLE
- ENABLE_ADMIN
- DRIFT_HOME
ports:
- "3000:3000"
client:
build:
context: ./client
args:
- API_URL=http://server:3000
container_name: client
restart: unless-stopped
user: 1000:1000
environment:
- API_URL=http://server:3000
- SECRET_KEY=secret # change me!
ports:
- "3001:3001"

16
jest.config.js Normal file
View file

@ -0,0 +1,16 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
setupFiles: ["<rootDir>/src/test/setup-tests.ts"],
// TODO: update to app dir
moduleNameMapper: {
"@lib/(.*)": "<rootDir>/src/lib/$1",
"@components/(.*)": "<rootDir>/src/app/components/$1",
"\\.(css)$": "identity-obj-proxy"
},
testPathIgnorePatterns: ["/node_modules/", "/.next/"],
transform: {
"^.+\\.(js|jsx|ts|tsx)$": "ts-jest"
}
}

View file

@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

46
next.config.mjs Normal file
View file

@ -0,0 +1,46 @@
import bundleAnalyzer from "@next/bundle-analyzer"
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
appDir: true
},
rewrites() {
return [
{
source: "/file/raw/:id",
destination: `/api/raw/:id`
},
{
source: "/signout",
destination: `/api/auth/signout`
}
]
},
images: {
domains: ["avatars.githubusercontent.com"]
},
env: {
NEXT_PUBLIC_DRIFT_URL:
process.env.DRIFT_URL ||
(process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "http://localhost:3000")
},
eslint: {
ignoreDuringBuilds: process.env.VERCEL_ENV !== "production"
},
typescript: {
ignoreBuildErrors: process.env.VERCEL_ENV !== "production"
},
modularizeImports: {
"react-feather": {
transform: "react-feather/dist/icons/{{kebabCase member}}"
}
}
}
export default process.env.ANALYZE === "true"
? bundleAnalyzer({ enabled: true })(nextConfig)
: nextConfig

113
package.json Normal file
View file

@ -0,0 +1,113 @@
{
"name": "drift",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --port 3000",
"build": "next build",
"start": "next start --port 3000",
"lint": "next lint && prettier --list-different --config .prettierrc 'src/{components,lib,app,pages}/**/*.{ts,tsx}' --write",
"analyze": "cross-env ANALYZE=true next build",
"find:unused": "next-unused",
"prisma": "prisma",
"jest": "jest"
},
"dependencies": {
"@next-auth/prisma-adapter": "^1.0.7",
"@next/eslint-plugin-next": "13.4.11-canary.0",
"@prisma/client": "^5.0.0",
"@radix-ui/react-alert-dialog": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-navigation-menu": "^1.1.3",
"@radix-ui/react-popover": "^1.0.5",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.5",
"@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
"@tailwindcss/typography": "^0.5.9",
"class-variance-authority": "^0.6.0",
"client-only": "^0.0.1",
"client-zip": "2.3.1",
"cmdk": "^0.2.0",
"date-fns": "^2.30.0",
"jest": "^29.5.0",
"lodash.debounce": "^4.0.8",
"next": "13.4.11-canary.1",
"next-auth": "^4.22.3",
"next-themes": "^0.2.1",
"react": "18.2.0",
"react-cookie": "^4.1.1",
"react-datepicker": "4.10.0",
"react-day-picker": "^8.8.0",
"react-dom": "18.2.0",
"react-dropzone": "14.2.3",
"react-error-boundary": "^4.0.4",
"react-feather": "^2.0.10",
"react-hot-toast": "2.4.1",
"server-only": "^0.0.1",
"swr": "^2.2.0",
"tailwind-merge": "^1.13.0",
"tailwindcss-animate": "^1.0.5",
"textarea-markdown-editor": "1.0.4",
"ts-jest": "^29.1.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@next/bundle-analyzer": "13.4.11-canary.0",
"@total-typescript/ts-reset": "^0.4.2",
"@types/bcrypt": "^5.0.0",
"@types/git-http-backend": "^1.0.1",
"@types/jest": "^29.4.1",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "18.15.11",
"@types/react": "18.0.35",
"@types/react-datepicker": "4.10.0",
"@types/react-dom": "18.0.11",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"@wcj/markdown-to-html": "^2.2.1",
"autoprefixer": "^10.4.14",
"clsx": "^1.2.1",
"cross-env": "7.0.3",
"csstype": "^3.1.2",
"dotenv": "^16.0.3",
"eslint": "8.38.0",
"eslint-config-next": "13.4.11-canary.1",
"jest-mock-extended": "^3.0.3",
"next-unused": "0.0.6",
"postcss": "^8.4.21",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-hover-media-feature": "^1.0.2",
"postcss-nested": "^6.0.1",
"postcss-preset-env": "^8.4.1",
"prettier": "2.8.7",
"prettier-plugin-tailwindcss": "^0.3.0",
"prisma": "^5.0.0",
"tailwindcss": "^3.3.2",
"typescript": "5.1.6",
"typescript-plugin-css-modules": "5.0.1"
},
"optionalDependencies": {
"sharp": "^0.32.0"
},
"next-unused": {
"alias": {
"@components": "components/",
"@lib": "src/lib/",
"@styles": "styles/"
},
"include": [
"components",
"lib"
]
},
"prisma": {
"schema": "src/prisma/schema.prisma"
},
"overrides": {
"react": "18.2.0",
"react-dom": "18.2.0"
}
}

9208
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

7
postcss.config.js Normal file
View file

@ -0,0 +1,7 @@
module.exports = {
plugins: {
"@tailwindcss/nesting": {},
tailwindcss: {},
autoprefixer: {}
}
}

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

Before

Width:  |  Height:  |  Size: 653 B

After

Width:  |  Height:  |  Size: 653 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 930 B

After

Width:  |  Height:  |  Size: 930 B

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

3
renovate.json Normal file
View file

@ -0,0 +1,3 @@
{
"extends": ["config:base", "group:allNonMajor", "schedule:earlyMondays"]
}

View file

@ -1 +0,0 @@
node_modules/

3
server/.gitignore vendored
View file

@ -1,3 +0,0 @@
.env
node_modules/
dist/

View file

@ -1,38 +0,0 @@
# Install dependencies only when needed
FROM node:16-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat git
WORKDIR /app
COPY package.json yarn.lock tsconfig.json tslint.json ./
RUN yarn install --frozen-lockfile
# If using npm with a `package-lock.json` comment out above and use below instead
# COPY package.json package-lock.json ./
# RUN npm ci
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN yarn build
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 drift
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER drift
EXPOSE 3000
ENV PORT 3000
CMD ["node", "dist/index.js"]

View file

@ -1,4 +0,0 @@
import * as dotenv from 'dotenv';
dotenv.config();
import './src/server';

View file

@ -1,4 +0,0 @@
export default {
port: process.env.PORT || 3000,
jwt_secret: process.env.JWT_SECRET || 'myjwtsecret',
}

View file

@ -1,30 +0,0 @@
import { NextFunction, Request, Response } from 'express';
import * as jwt from 'jsonwebtoken';
import config from '../config';
import { User as UserModel } from '../models/User';
export interface User {
id: string;
}
export interface UserJwtRequest extends Request {
user?: User;
}
export default function authenticateToken(req: UserJwtRequest, res: Response, next: NextFunction) {
const authHeader = req.headers['authorization']
const token = authHeader && authHeader.split(' ')[1]
if (token == null) return res.sendStatus(401)
jwt.verify(token, config.jwt_secret, async (err: any, user: any) => {
if (err) return res.sendStatus(403)
const userObj = await UserModel.findByPk(user.id);
if (!userObj) {
return res.sendStatus(403);
}
req.user = user
next()
})
}

View file

@ -1,49 +0,0 @@
import { BelongsTo, Column, CreatedAt, DataType, ForeignKey, IsUUID, Model, PrimaryKey, Scopes, Table } from 'sequelize-typescript';
import { Post } from './Post';
import { User } from './User';
@Scopes(() => ({
full: {
include: [{
model: User,
through: { attributes: [] },
},
{
model: Post,
through: { attributes: [] },
}]
}
}))
@Table
export class File extends Model {
@IsUUID(4)
@PrimaryKey
@Column({
type: DataType.UUID,
defaultValue: DataType.UUIDV4,
})
id!: string
@Column
title!: string;
@Column
content!: string;
@Column
sha!: string;
@ForeignKey(() => User)
@BelongsTo(() => User, 'userId')
user!: User;
@ForeignKey(() => Post)
@BelongsTo(() => Post, 'postId')
post!: Post;
@CreatedAt
@Column
createdAt!: Date;
}

View file

@ -1,54 +0,0 @@
import { BelongsToMany, Column, CreatedAt, DataType, HasMany, IsUUID, Model, PrimaryKey, Scopes, Table, UpdatedAt } from 'sequelize-typescript';
import { PostAuthor } from './PostAuthor';
import { User } from './User';
import { File } from './File';
@Scopes(() => ({
user: {
include: [{
model: User,
through: { attributes: [] },
}],
},
full: {
include: [{
model: User,
through: { attributes: [] },
},
{
model: File,
through: { attributes: [] },
}]
}
}))
@Table
export class Post extends Model {
@IsUUID(4)
@PrimaryKey
@Column({
type: DataType.UUID,
defaultValue: DataType.UUIDV4,
})
id!: string
@Column
title!: string;
@BelongsToMany(() => User, () => PostAuthor)
users?: User[];
@HasMany(() => File)
files?: File[];
@CreatedAt
@Column
createdAt!: Date;
@Column
visibility!: string;
@UpdatedAt
@Column
updatedAt!: Date;
}

View file

@ -1,22 +0,0 @@
import { Model, Column, Table, ForeignKey, IsUUID, PrimaryKey, DataType } from "sequelize-typescript";
import { Post } from "./Post";
import { User } from "./User";
@Table
export class PostAuthor extends Model {
@IsUUID(4)
@PrimaryKey
@Column({
type: DataType.UUID,
defaultValue: DataType.UUIDV4,
})
id!: string
@ForeignKey(() => Post)
@Column
postId!: number;
@ForeignKey(() => User)
@Column
authorId!: number;
}

View file

@ -1,47 +0,0 @@
import { Model, Column, Table, BelongsToMany, Scopes, CreatedAt, UpdatedAt, IsUUID, PrimaryKey, DataType } from "sequelize-typescript";
import { Post } from "./Post";
import { PostAuthor } from "./PostAuthor";
@Scopes(() => ({
posts: {
include: [
{
model: Post,
through: { attributes: [] },
},
],
},
withoutPassword: {
attributes: {
exclude: ["password"]
}
}
}))
@Table
export class User extends Model {
@IsUUID(4)
@PrimaryKey
@Column({
type: DataType.UUID,
defaultValue: DataType.UUIDV4,
})
id!: string
@Column
username!: string;
@Column
password!: string;
@BelongsToMany(() => Post, () => PostAuthor)
posts?: Post[];
@CreatedAt
@Column
createdAt!: Date;
@UpdatedAt
@Column
updatedAt!: Date;
}

View file

@ -1,8 +0,0 @@
import {Sequelize} from 'sequelize-typescript';
export const sequelize = new Sequelize({
dialect: 'sqlite',
database: 'movies',
storage: ':memory:',
models: [__dirname + '/models']
});

View file

@ -1,39 +0,0 @@
{
"name": "sequelize-typescript-starter",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"start": "ts-node index.ts",
"dev": "nodemon index.ts",
"build": "tsc -p ."
},
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.1",
"body-parser": "^1.18.2",
"cors": "^2.8.5",
"dotenv": "^16.0.0",
"express": "^4.16.2",
"express-jwt": "^6.1.1",
"jsonwebtoken": "^8.5.1",
"nodemon": "^2.0.15",
"reflect-metadata": "^0.1.10",
"sequelize": "^6.17.0",
"sequelize-typescript": "^2.1.3",
"sqlite3": "https://github.com/mapbox/node-sqlite3#918052b538b0effe6c4a44c74a16b2749c08a0d2",
"strong-error-handler": "^4.0.0"
},
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.0.39",
"@types/express-jwt": "^6.0.4",
"@types/jsonwebtoken": "^8.5.8",
"@types/node": "^17.0.21",
"ts-node": "^10.6.0",
"tslint": "^6.1.3",
"typescript": "^4.6.2"
}
}

View file

@ -1,24 +0,0 @@
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as errorhandler from 'strong-error-handler';
import * as cors from 'cors';
import { posts, users, auth } from './routes';
export const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json({ limit: '5mb' }));
const corsOptions = {
origin: `http://localhost:3001`,
};
app.use(cors(corsOptions));
app.use("/auth", auth)
app.use("/posts", posts)
app.use("/users", users)
app.use(errorhandler({
debug: process.env.ENV !== 'production',
log: true,
}));

View file

@ -1,78 +0,0 @@
import { Router } from 'express'
// import { Movie } from '../models/Post'
import { genSalt, hash, compare } from "bcrypt"
import { User } from '../../lib/models/User'
import { sign } from 'jsonwebtoken'
import config from '../../lib/config'
import jwt from '../../lib/middleware/jwt'
export const auth = Router()
auth.post('/signup', async (req, res, next) => {
try {
if (!req.body.username || !req.body.password) {
throw new Error("Please provide a username and password")
}
const username = req.body.username.toLowerCase();
const existingUser = await User.findOne({ where: { username: username } })
if (existingUser) {
throw new Error("Username already exists")
}
const salt = await genSalt(10)
const user = {
username: username as string,
password: await hash(req.body.password, salt)
}
const created_user = await User.create(user);
const token = generateAccessToken(created_user.id);
res.status(201).json({ token: token, userId: created_user.id })
} catch (e) {
next(e);
}
});
auth.post('/signin', async (req, res, next) => {
try {
if (!req.body.username || !req.body.password) {
throw new Error("Missing username or password")
}
const username = req.body.username.toLowerCase();
const user = await User.findOne({ where: { username: username } });
if (!user) {
throw new Error("User does not exist");
}
const password_valid = await compare(req.body.password, user.password);
if (password_valid) {
const token = generateAccessToken(user.id);
res.status(200).json({ token: token, userId: user.id });
} else {
throw new Error("Password Incorrect");
}
} catch (e) {
next(e);
}
});
function generateAccessToken(id: string) {
return sign({ id: id }, config.jwt_secret, { expiresIn: '2d' });
}
auth.get("/verify-token", jwt, async (req, res, next) => {
try {
res.status(200).json({
message: "You are authenticated"
})
}
catch (e) {
next(e);
}
})

View file

@ -1,3 +0,0 @@
export { auth } from './auth';
export { posts } from './posts';
export { users } from './users';

Some files were not shown because too many files have changed in this diff Show more