├── .github
├── FUNDING.yml
└── workflows
│ ├── check.yml
│ └── release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .vscode
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── app
├── .eslintrc.cjs
├── .gitignore
├── components.json
├── index.html
├── package.json
├── postcss.config.js
├── public
│ ├── fonts
│ │ ├── Inter-roman.var.woff2
│ │ └── JetBrainsMono-Regular.woff2
│ ├── grid
│ │ ├── grid-dark.svg
│ │ └── grid-light.svg
│ └── images
│ │ ├── logo.png
│ │ └── logo.svg
├── src-tauri
│ ├── .gitignore
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── build.rs
│ ├── icons
│ │ ├── 128x128.png
│ │ ├── 128x128@2x.png
│ │ ├── 32x32.png
│ │ ├── Square107x107Logo.png
│ │ ├── Square142x142Logo.png
│ │ ├── Square150x150Logo.png
│ │ ├── Square284x284Logo.png
│ │ ├── Square30x30Logo.png
│ │ ├── Square310x310Logo.png
│ │ ├── Square44x44Logo.png
│ │ ├── Square71x71Logo.png
│ │ ├── Square89x89Logo.png
│ │ ├── StoreLogo.png
│ │ ├── icon.icns
│ │ ├── icon.ico
│ │ └── icon.png
│ ├── src
│ │ └── main.rs
│ └── tauri.conf.json
├── src
│ ├── components
│ │ ├── changeTheme.tsx
│ │ ├── container.tsx
│ │ ├── errorElement.tsx
│ │ ├── explorer
│ │ │ └── index.tsx
│ │ ├── file
│ │ │ ├── createFile.tsx
│ │ │ ├── deleteFile.tsx
│ │ │ ├── fileItem.tsx
│ │ │ ├── fileList.tsx
│ │ │ ├── openFile.tsx
│ │ │ └── renameFile.tsx
│ │ ├── folder
│ │ │ ├── createFolder.tsx
│ │ │ └── index.tsx
│ │ ├── monaco
│ │ │ └── index.tsx
│ │ ├── pageNavbar
│ │ │ └── index.tsx
│ │ ├── providers
│ │ │ └── index.tsx
│ │ ├── search.tsx
│ │ ├── settings
│ │ │ ├── account.tsx
│ │ │ ├── appearance.tsx
│ │ │ ├── settingsGroup.tsx
│ │ │ └── userSettings.tsx
│ │ ├── sidebar
│ │ │ ├── index.tsx
│ │ │ ├── shared.ts
│ │ │ └── sidebarGroup.tsx
│ │ ├── tip.tsx
│ │ └── workspaces
│ │ │ ├── index.tsx
│ │ │ └── manageWorkspaces.tsx
│ ├── data
│ │ └── fileExtensions.ts
│ ├── main.tsx
│ ├── providers
│ │ └── themeProvider.tsx
│ ├── routes
│ │ ├── editor.tsx
│ │ ├── index.tsx
│ │ └── settings.tsx
│ ├── store
│ │ ├── appStore.ts
│ │ ├── userStore.ts
│ │ └── workspaceStore.ts
│ ├── styles
│ │ ├── globals.css
│ │ ├── monaco-theme.json
│ │ └── tiptap-code.css
│ ├── utils
│ │ ├── openLink.ts
│ │ └── text.ts
│ └── vite-env.d.ts
├── tailwind.config.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── package.json
├── packages
├── editor
│ ├── .eslintrc.cjs
│ ├── package.json
│ ├── src
│ │ ├── components
│ │ │ ├── editor.tsx
│ │ │ ├── menu.tsx
│ │ │ └── tooltip.tsx
│ │ ├── extensions
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── types
│ │ │ ├── editorProps.d.ts
│ │ │ ├── menuProps.d.ts
│ │ │ └── tooltipProps.d.ts
│ │ └── utils
│ │ │ └── cn.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── functions
│ ├── .eslintrc.cjs
│ ├── package.json
│ ├── src
│ │ ├── checkDirFile.ts
│ │ ├── createFolder.ts
│ │ ├── createUpdateFile.ts
│ │ ├── deleteFile.ts
│ │ ├── getAllPlatformInfo.ts
│ │ ├── getFileName.ts
│ │ ├── getFolderName.ts
│ │ ├── index.ts
│ │ ├── openFile.ts
│ │ ├── readFiles.ts
│ │ └── selectFolder.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── shared
│ └── tsconfig.json
├── tailwind-config
│ ├── package.json
│ └── tailwind.config.ts
└── ui
│ ├── .eslintrc.cjs
│ ├── components.json
│ ├── package.json
│ ├── postcss.config.cjs
│ ├── src
│ ├── components
│ │ ├── alert.tsx
│ │ ├── button.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── externalLink.tsx
│ │ ├── formGroup.tsx
│ │ ├── input.tsx
│ │ ├── popover.tsx
│ │ ├── radio-group.tsx
│ │ ├── tabs.tsx
│ │ └── tooltip.tsx
│ ├── extend
│ │ └── prose.ts
│ ├── index.tsx
│ ├── styles
│ │ └── globals.css
│ └── utils
│ │ └── cn.ts
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── prettier.config.cjs
├── turbo.json
└── website
├── .eslintrc.json
├── next.config.mjs
├── package.json
├── postcss.config.cjs
├── public
├── fonts
│ ├── Inter-roman.var.woff2
│ └── JetBrainsMono-Regular.woff2
└── images
│ ├── logo.png
│ ├── logo.svg
│ ├── screenshot_en.png
│ └── screenshot_es.png
├── src
├── app
│ ├── api
│ │ └── hello
│ │ │ └── route.ts
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── cardSpotlight.tsx
│ ├── container.tsx
│ ├── hero.tsx
│ ├── icons
│ │ ├── mac.tsx
│ │ ├── typethings.tsx
│ │ └── windows.tsx
│ ├── navbar.tsx
│ └── socials.tsx
├── global
│ └── data.ts
└── styles
│ └── globals.css
├── tailwind.config.ts
└── tsconfig.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [pheralb]
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | name: 🔎 Check
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - dev
8 |
9 | concurrency: ${{ github.workflow }} - ${{ github.ref }}
10 |
11 | jobs:
12 | Typecheck:
13 | name: Packages Types
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 |
18 | - uses: pnpm/action-setup@v2
19 | with:
20 | version: 8
21 |
22 | - name: Install dependencies
23 | run: pnpm install
24 |
25 | - name: App Typecheck
26 | run: pnpm run typecheck-app
27 |
28 | - name: Editor Typecheck
29 | run: pnpm run typecheck-editor
30 |
31 | - name: UI Library Typecheck
32 | run: pnpm run typecheck-editor
33 |
34 | - name: Functions Typecheck
35 | run: pnpm run typecheck-editor
36 |
37 | Website:
38 | name: Build Website
39 | runs-on: ubuntu-latest
40 | steps:
41 | - uses: actions/checkout@v3
42 |
43 | - uses: pnpm/action-setup@v2
44 | with:
45 | version: 8
46 |
47 | - name: Install dependencies
48 | run: pnpm install
49 |
50 | - name: Build Website
51 | run: pnpm turbo run build --filter=website
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: "📦 Publish"
2 | on:
3 | push:
4 | branches:
5 | - release
6 |
7 | jobs:
8 | publish-tauri:
9 | permissions:
10 | contents: write
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | platform: [macos-latest, ubuntu-20.04, windows-latest]
15 |
16 | runs-on: ${{ matrix.platform }}
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Setup Node 20
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: 20
23 |
24 | - name: Install Rust Stable
25 | uses: dtolnay/rust-toolchain@stable
26 |
27 | - name: Install Dependencies (ubuntu only)
28 | if: matrix.platform == 'ubuntu-20.04'
29 | run: |
30 | sudo apt-get update
31 | sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
32 |
33 | - name: Install Dependencies
34 | run: pnpm install
35 | - uses: tauri-apps/tauri-action@v0
36 | env:
37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38 | with:
39 | tagName: typethings-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version
40 | releaseName: "v__VERSION__"
41 | releaseBody: "See the assets to download this version and install."
42 | releaseDraft: true
43 | prerelease: false
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules
3 | package-lock.json
4 | yarn.lock
5 | .pnp
6 | .pnp.js
7 |
8 | # Testing
9 | coverage
10 |
11 | # Build folders
12 | .next/
13 | next-env.d.ts
14 | out/
15 | build
16 | dist
17 | dist-ssr
18 | *.local
19 |
20 | # Astro generated types
21 | .astro/
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 |
32 | # local env files
33 | .env
34 | .env.local
35 | .env.development.local
36 | .env.test.local
37 | .env.production.local
38 |
39 | # turbo
40 | .turbo
41 |
42 | # vercel
43 | .vercel
44 |
45 | # Editor directories and files:
46 | .idea
47 | .DS_Store
48 | *.suo
49 | *.ntvs*
50 | *.njsproj
51 | *.sln
52 | *.sw?
53 |
54 | # Other:
55 | tsconfig.tsbuildinfo
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers = true
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18.18.0
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "tauri-apps.tauri-vscode",
4 | "rust-lang.rust-analyzer",
5 | "tamasfe.even-better-toml",
6 | "esbenp.prettier-vscode",
7 | "dbaeumer.vscode-eslint"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "rust-analyzer.linkedProjects": ["app/src-tauri/Cargo.toml"]
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
34 |
35 |
36 |
37 |
38 |
39 | 
40 | [](https://actions-badge.atrox.dev/pheralb/typethings/goto?ref=main)
41 | 
42 | 
43 | 
44 | 
45 | 
46 |
47 |
48 |
49 | > [!IMPORTANT]
50 | > This is a work-in-progress and not the finished product.
51 | > Typethings will be constantly updated and is not yet ready for its first release.
52 |
53 | ## Introduction
54 |
55 | [**Typethings**](#) is an open source markdown editor built with [Tauri](https://tauri.app) and [React](https://react.dev). It is designed to be a simple, fast and beautiful for everyone.
56 |
57 | - [x] Create, read, delete markdown files.
58 | - [x] Create and delete workspaces.
59 | - [x] Open markdown files from a specific directory.
60 | - [x] Show files from workspace.
61 | - [x] CMD + K to search app settings & files.
62 | - [x] Support code with syntax highlighting.
63 | - [x] Light and dark mode.
64 | - [x] Works completely offline.
65 |
66 | ## 📦 Download
67 |
68 | **Download the latest release for your platform:**
69 |
70 | | | Platform | Version | Download |
71 | | --- | -------- | ---------------------------------------------------------------------------- | -------- |
72 | | ☁️ | Windows |  | _Soon_ |
73 | | ☁️ | Linux |  | _Soon_ |
74 | | ☁️ | MacOS |  | _Soon_ |
75 |
76 | ## 🚀 Getting Started
77 |
78 | To get a local copy up and running, please follow these simple steps.
79 |
80 | **Prerequisites:**
81 |
82 | - [Node.js +18 (LTS recommended)](https://nodejs.org/). Then run `node --version` in your terminal to check if it's installed correctly.
83 | - [pnpm (we are using +8.8.0)](https://pnpm.io/). Install with npm: `npm i pnpm -g`. Then run `pnpm --version` in your terminal to check if it's installed correctly.
84 | - [Visual Studio Code](https://code.visualstudio.com/) (recommended) or [Lapce](https://lapce.dev/).
85 | - Only for Windows: [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/es/visual-cpp-build-tools/).
86 | - Rust. For Windows x64: [click here](https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe). For Linux: [click here](https://forge.rust-lang.org/infra/other-installation-methods.html#other-ways-to-install-rustup). Then, run `rustc --version` in your terminal to check if it's installed correctly.
87 |
88 | **Setup:**
89 |
90 | 1. Clone or [fork](https://github.com/pheralb/typethings/fork) this repository:
91 |
92 | ```bash
93 | git clone git@github.com:pheralb/typethings.git
94 | ```
95 |
96 | 2. Install dependencies:
97 |
98 | ```bash
99 | # Access the project folder:
100 | cd typethings
101 |
102 | # Install dependencies:
103 | pnpm install
104 | ```
105 |
106 | 3. Run the app:
107 |
108 | ```bash
109 | # Run all monorepo apps, websites and packages:
110 | pnpm dev
111 |
112 | # Run only website dev server:
113 | pnpm dev:web
114 | ```
115 |
116 | ## 🤔 What's inside?
117 |
118 | ### Desktop App:
119 |
120 | Built with:
121 |
122 | - [Tauri](https://tauri.studio/en/) - Build smaller, faster, and more secure desktop applications with a web frontend.
123 | - [React](https://reactjs.org/) - A JavaScript library for building user interfaces.
124 | - [Zustand](https://docs.pmnd.rs/zustand/getting-started/introduction) - For state management in React.
125 | - [React Router v6](https://reactrouter.com/) - For routing in React.
126 |
127 | ### Website:
128 |
129 | Built with:
130 |
131 | - [Next.js](https://nextjs.org/) - The React Framework for Production.
132 | - [@typethings/ui](https://github.com/pheralb/typethings/tree/main/packages/ui) - A set of accessible UI components.
133 |
134 | ### Packages:
135 |
136 | For all websites & apps:
137 |
138 | | Package | Description |
139 | | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
140 | | [@typethings/editor](https://github.com/pheralb/typethings/tree/main/packages/editor) | An unstyled primitives based on [Tiptap](https://tiptap.dev/) for building your custom WYSIWYG editor. |
141 | | [@typethings/functions](https://github.com/pheralb/typethings/tree/main/packages/functions) | A set of files/folders functions using Tauri API. |
142 | | [@typethings/ui](https://github.com/pheralb/typethings/tree/main/packages/ui) | A set of accessible UI components. Built with [React](https://react.dev), [shadcn/ui](https://ui.shadcn.com/) and [Tailwind CSS](https://tailwindcss.com/). |
143 | | [@typethings/tailwind-config](https://github.com/pheralb/typethings/tree/main/packages/ui) | [Tailwind CSS](https://tailwindcss.com/) configuration for Typethings App. |
144 |
145 | ## 📝 License
146 |
147 | - [Apache License 2.0](https://github.com/pheralb/typethings/blob/main/LICENSE).
148 |
--------------------------------------------------------------------------------
/app/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | "eslint:recommended",
6 | "plugin:@typescript-eslint/recommended",
7 | "plugin:react-hooks/recommended",
8 | ],
9 | ignorePatterns: ["dist", ".eslintrc.cjs"],
10 | parser: "@typescript-eslint/parser",
11 | plugins: ["react-refresh"],
12 | rules: {
13 | "react-refresh/only-export-components": [
14 | "warn",
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs:
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | # Dependencies:
11 | node_modules
12 | dist
13 | dist-ssr
14 | *.local
15 | package-lock.json
16 | pnpm-lock.yaml
17 | yarn.lock
18 |
19 | # Editor directories and files:
20 | .vscode/*
21 | !.vscode/extensions.json
22 | .idea
23 | .DS_Store
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
--------------------------------------------------------------------------------
/app/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/styles/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tauri + React + TS
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typethings/app",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "tauri dev",
8 | "icon": "tauri icon public/images/logo.png",
9 | "dev:vite": "vite",
10 | "build": "tsc && vite build",
11 | "build-tauri": "tauri build",
12 | "preview": "vite preview",
13 | "typecheck": "tsc --noEmit",
14 | "tauri": "tauri"
15 | },
16 | "dependencies": {
17 | "@monaco-editor/react": "4.6.0",
18 | "@tauri-apps/api": "1.5.0",
19 | "@typethings/editor": "workspace:*",
20 | "@typethings/functions": "workspace:*",
21 | "@typethings/tailwind-config": "workspace:*",
22 | "@typethings/ui": "workspace:*",
23 | "boring-avatars": "1.10.1",
24 | "class-variance-authority": "0.7.0",
25 | "lucide-react": "0.286.0",
26 | "react": "18.2.0",
27 | "react-dom": "18.2.0",
28 | "react-hook-form": "7.47.0",
29 | "react-hotkeys-hook": "4.4.1",
30 | "react-router-dom": "6.16.0",
31 | "sonner": "1.0.3",
32 | "zustand": "4.4.3"
33 | },
34 | "devDependencies": {
35 | "@tauri-apps/cli": "1.5.2",
36 | "@typescript-eslint/eslint-plugin": "6.7.5",
37 | "@typescript-eslint/parser": "6.7.5",
38 | "@vitejs/plugin-react": "4.1.0",
39 | "autoprefixer": "10.4.16",
40 | "eslint": "8.51.0",
41 | "eslint-plugin-react": "7.33.2",
42 | "postcss": "8.4.31",
43 | "tailwindcss": "3.3.3",
44 | "typescript": "5.2.2",
45 | "vite": "4.4.11"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | "postcss-import": {},
4 | "tailwindcss/nesting": {},
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/app/public/fonts/Inter-roman.var.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/public/fonts/Inter-roman.var.woff2
--------------------------------------------------------------------------------
/app/public/fonts/JetBrainsMono-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/public/fonts/JetBrainsMono-Regular.woff2
--------------------------------------------------------------------------------
/app/public/grid/grid-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/public/grid/grid-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/public/images/logo.png
--------------------------------------------------------------------------------
/app/public/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 |
--------------------------------------------------------------------------------
/app/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "typethings"
3 | version = "0.1.0"
4 | description = "A beautiful, minimal, and fast markdown editor"
5 | authors = ["pheralb"]
6 | license = "Apache-2.0"
7 | repository = "https://github.com/pheralb/typethings"
8 | edition = "2021"
9 |
10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11 |
12 | [build-dependencies]
13 | tauri-build = { version = "1.4", features = [] }
14 |
15 | [dependencies]
16 | tauri = { version = "1.4", features = [
17 | "os-all",
18 | "process-relaunch-dangerous-allow-symlink-macos",
19 | "process-exit",
20 | "process-relaunch",
21 | "window-set-title",
22 | "path-all",
23 | "fs-remove-file",
24 | "fs-read-dir",
25 | "fs-write-file",
26 | "fs-rename-file",
27 | "fs-read-file",
28 | "fs-create-dir",
29 | "fs-remove-dir",
30 | "fs-exists",
31 | "dialog-confirm",
32 | "dialog-open",
33 | "dialog-save",
34 | "shell-open",
35 | ] }
36 | serde = { version = "1.0", features = ["derive"] }
37 | serde_json = "1.0"
38 | window-vibrancy = "0.4.2"
39 |
40 | [features]
41 | # this feature is used for production builds or when `devPath` points to the filesystem
42 | # DO NOT REMOVE!!
43 | custom-protocol = ["tauri/custom-protocol"]
44 |
--------------------------------------------------------------------------------
/app/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/app/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/app/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/app/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/app/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/app/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/app/src-tauri/src/main.rs:
--------------------------------------------------------------------------------
1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!!
2 | // #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3 | #![cfg_attr(
4 | all(not(debug_assertions), target_os = "windows"),
5 | windows_subsystem = "windows"
6 | )]
7 |
8 | use tauri::Manager;
9 | use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial};
10 |
11 | fn main() {
12 | tauri::Builder::default()
13 | .setup(|app| {
14 | let window: tauri::Window = app.get_window("main").unwrap();
15 |
16 | #[cfg(target_os = "macos")]
17 | apply_vibrancy(&window, NSVisualEffectMaterial::HudWindow, None, None)
18 | .expect("Unsupported platform! 'apply_vibrancy' is only supported on macOS");
19 |
20 | Ok(())
21 | })
22 | .run(tauri::generate_context!())
23 | .expect("error while running tauri application");
24 | }
25 |
--------------------------------------------------------------------------------
/app/src-tauri/tauri.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "build": {
3 | "beforeDevCommand": "npm run dev:vite",
4 | "beforeBuildCommand": "npm run build",
5 | "devPath": "http://localhost:1420",
6 | "distDir": "../dist",
7 | "withGlobalTauri": false
8 | },
9 | "package": {
10 | "productName": "typethings",
11 | "version": "0.0.0"
12 | },
13 | "tauri": {
14 | "allowlist": {
15 | "all": false,
16 | "process": {
17 | "all": false,
18 | "exit": true,
19 | "relaunch": true,
20 | "relaunchDangerousAllowSymlinkMacos": false
21 | },
22 | "os": {
23 | "all": true
24 | },
25 | "window": {
26 | "all": false,
27 | "center": false,
28 | "close": false,
29 | "create": false,
30 | "hide": false,
31 | "maximize": false,
32 | "minimize": false,
33 | "print": false,
34 | "requestUserAttention": false,
35 | "setAlwaysOnTop": false,
36 | "setClosable": false,
37 | "setContentProtected": false,
38 | "setCursorGrab": false,
39 | "setCursorIcon": false,
40 | "setCursorPosition": false,
41 | "setCursorVisible": false,
42 | "setDecorations": false,
43 | "setFocus": false,
44 | "setFullscreen": false,
45 | "setIcon": false,
46 | "setIgnoreCursorEvents": false,
47 | "setMaxSize": false,
48 | "setMaximizable": false,
49 | "setMinSize": false,
50 | "setMinimizable": false,
51 | "setPosition": false,
52 | "setResizable": false,
53 | "setSize": false,
54 | "setSkipTaskbar": false,
55 | "setTitle": true,
56 | "show": false,
57 | "startDragging": false,
58 | "unmaximize": false,
59 | "unminimize": false
60 | },
61 | "shell": {
62 | "all": false,
63 | "open": true
64 | },
65 | "dialog": {
66 | "all": false,
67 | "save": true,
68 | "open": true,
69 | "confirm": true
70 | },
71 | "path": {
72 | "all": true
73 | },
74 | "fs": {
75 | "all": false,
76 | "copyFile": false,
77 | "createDir": true,
78 | "exists": true,
79 | "readDir": true,
80 | "readFile": true,
81 | "removeDir": true,
82 | "removeFile": true,
83 | "renameFile": true,
84 | "scope": ["$DOCUMENT/*", "$DESKTOP/**/**"],
85 | "writeFile": true
86 | }
87 | },
88 | "bundle": {
89 | "active": true,
90 | "targets": "all",
91 | "identifier": "com.typethings.dev",
92 | "icon": [
93 | "icons/32x32.png",
94 | "icons/128x128.png",
95 | "icons/128x128@2x.png",
96 | "icons/icon.icns",
97 | "icons/icon.ico"
98 | ]
99 | },
100 | "security": {
101 | "csp": null
102 | },
103 | "windows": [
104 | {
105 | "fullscreen": false,
106 | "resizable": true,
107 | "title": "Typethings",
108 | "width": 1100,
109 | "height": 600
110 | }
111 | ]
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/components/changeTheme.tsx:
--------------------------------------------------------------------------------
1 | import { Moon, Sun } from "lucide-react";
2 |
3 | import {
4 | Button,
5 | DropdownMenu,
6 | DropdownMenuContent,
7 | DropdownMenuItem,
8 | DropdownMenuTrigger,
9 | } from "@typethings/ui";
10 |
11 | import { useTheme } from "@/providers/themeProvider";
12 |
13 | const ChangeTheme = () => {
14 | const { setTheme } = useTheme();
15 | return (
16 |
17 |
18 |
19 |
20 |
21 | Toggle theme
22 |
23 |
24 |
25 | setTheme("light")}>
26 | Light
27 |
28 | setTheme("dark")}>
29 | Dark
30 |
31 | setTheme("system")}>
32 | System
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default ChangeTheme;
40 |
--------------------------------------------------------------------------------
/app/src/components/container.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import { cn } from "@typethings/ui";
3 |
4 | interface ContainerProps {
5 | children: ReactNode;
6 | className?: string;
7 | }
8 |
9 | const Container = (props: ContainerProps) => {
10 | return (
11 |
17 | {props.children}
18 |
19 | );
20 | };
21 |
22 | export default Container;
23 |
--------------------------------------------------------------------------------
/app/src/components/errorElement.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useRouteError } from "react-router-dom";
3 | import { exit, relaunch } from "@tauri-apps/api/process";
4 |
5 | import { Button, buttonVariants, ExternalLink } from "@typethings/ui";
6 | import { AlertTriangle, ArrowUpRight, LogOut, RefreshCw } from "lucide-react";
7 |
8 | import { type OSInfo, getOSInfo } from "@typethings/functions";
9 |
10 | const ErrorElement = () => {
11 | const error = useRouteError() as any;
12 | const [platform, setPlatform] = useState();
13 |
14 | useEffect(() => {
15 | const handle = async () => {
16 | const platform = await getOSInfo();
17 | setPlatform(platform!);
18 | };
19 | handle();
20 | }, []);
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
Something went wrong.
29 |
30 |
38 |
39 |
OS info:
40 |
41 |
42 | Platform: {" "}
43 | {platform?.osType} - {platform?.platform}
44 |
45 |
46 | Version: {" "}
47 | {platform?.kernelVersion}
48 |
49 |
50 | Architecture: {" "}
51 | {platform?.architecture}
52 |
53 |
54 |
55 |
56 |
{
59 | await exit(0);
60 | }}
61 | >
62 |
63 | Exit
64 |
65 |
{
68 | await relaunch();
69 | }}
70 | >
71 |
72 | Reload
73 |
74 |
80 | Report
81 |
82 |
83 |
84 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default ErrorElement;
91 |
--------------------------------------------------------------------------------
/app/src/components/explorer/index.tsx:
--------------------------------------------------------------------------------
1 | import { useWorkspaceStore } from "@/store/workspaceStore";
2 | import { Button } from "@typethings/ui";
3 |
4 | import Folder from "@/components/folder";
5 | import ManageWorkspaces from "@/components/workspaces/manageWorkspaces";
6 | import FileList from "@/components/file/fileList";
7 |
8 | const Explorer = () => {
9 | const workspaces = useWorkspaceStore((state) => state.workspaces);
10 | return (
11 |
12 | {workspaces.length > 0 ? (
13 | workspaces.sort().map((workspace) => (
14 |
19 |
20 |
21 | ))
22 | ) : (
23 |
24 |
Add a workspace to get started.
25 |
31 | Get started
32 |
33 | }
34 | />
35 |
36 | )}
37 |
38 | );
39 | };
40 |
41 | export default Explorer;
42 |
--------------------------------------------------------------------------------
/app/src/components/file/createFile.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import { useState } from "react";
3 | import { useHotkeys } from "react-hotkeys-hook";
4 | import { useForm, SubmitHandler } from "react-hook-form";
5 | import { useNavigate } from "react-router-dom";
6 | import { useWorkspaceStore } from "@/store/workspaceStore";
7 | import { toast } from "sonner";
8 |
9 | import { checkDirFile, createFile } from "@typethings/functions";
10 |
11 | import {
12 | Input,
13 | Dialog,
14 | DialogContent,
15 | DialogFooter,
16 | DialogHeader,
17 | DialogTitle,
18 | DialogTrigger,
19 | FormGroup,
20 | Button,
21 | } from "@typethings/ui";
22 |
23 | import Tip from "@/components/tip";
24 | import Workspaces from "@/components/workspaces";
25 |
26 | interface iCreateFileProps {
27 | trigger: ReactNode;
28 | }
29 |
30 | interface iCreateFileInputs {
31 | title: string;
32 | path: string;
33 | }
34 |
35 | const CreateFile = (props: iCreateFileProps) => {
36 | const { register, handleSubmit } = useForm();
37 | const [openDialog, setOpenDialog] = useState(false);
38 | const selectFile = useWorkspaceStore((state) => state.setSelectedFile);
39 | const addFile = useWorkspaceStore((state) => state.addFileToWorkspace);
40 | const route = useNavigate();
41 | const selectedWorkspace = useWorkspaceStore(
42 | (state) => state.selectedWorkspace,
43 | );
44 | const deleteWorkspace = useWorkspaceStore((state) => state.deleteWorkspace);
45 | useHotkeys("ctrl+n", () => setOpenDialog(true));
46 |
47 | // Create New File function:
48 | const handleCreateFile: SubmitHandler = async (data) => {
49 | if (!selectedWorkspace) {
50 | toast.error("Please select a workspace.");
51 | return false;
52 | }
53 | try {
54 | const check = await checkDirFile(selectedWorkspace?.folderPath);
55 | if (!check) {
56 | toast.error("Directory not found.", {
57 | description: `The directory ${selectedWorkspace?.folderPath} was not found.`,
58 | });
59 | deleteWorkspace(selectedWorkspace?.folderPath);
60 | return;
61 | }
62 | const fullPath = await createFile({
63 | path: selectedWorkspace?.folderPath || "",
64 | filename: data.title,
65 | extension: "md",
66 | content: "",
67 | });
68 | const file = {
69 | name: `${data.title}.md`,
70 | path: fullPath,
71 | };
72 | addFile(selectedWorkspace.folderPath, file);
73 | selectFile({
74 | path: fullPath,
75 | content: "",
76 | });
77 | setOpenDialog(false);
78 | route(`/editor`);
79 | } catch (error) {
80 | console.error(error);
81 | }
82 | };
83 |
84 | return (
85 |
86 | {props.trigger}
87 |
88 |
89 | New file
90 |
91 |
103 |
104 |
105 |
106 | Workspace:
107 |
108 |
112 |
113 |
114 |
115 |
116 | setOpenDialog(false)}>
117 | Cancel
118 |
119 |
123 | Create
124 |
125 |
126 |
127 |
128 | );
129 | };
130 |
131 | export default CreateFile;
132 |
--------------------------------------------------------------------------------
/app/src/components/file/deleteFile.tsx:
--------------------------------------------------------------------------------
1 | import type { FileEntry } from "@tauri-apps/api/fs";
2 |
3 | import {
4 | Button,
5 | DialogContent,
6 | DialogClose,
7 | DialogDescription,
8 | DialogFooter,
9 | DialogHeader,
10 | DialogTitle,
11 | } from "@typethings/ui";
12 |
13 | import { toast } from "sonner";
14 | import { deleteFile } from "@typethings/functions";
15 | import { useNavigate } from "react-router-dom";
16 | import { useWorkspaceStore } from "@/store/workspaceStore";
17 |
18 | interface iDeleteFileProps {
19 | file: FileEntry;
20 | workspace: string;
21 | }
22 |
23 | const DeleteFile = (props: iDeleteFileProps) => {
24 | const removeFileStore = useWorkspaceStore(
25 | (state) => state.deleteFileFromWorkspace,
26 | );
27 | const router = useNavigate();
28 | // Delete function:
29 | const handleDeleteFile = async () => {
30 | try {
31 | router("/");
32 | await deleteFile({
33 | path: props.file.path,
34 | });
35 | removeFileStore(props.workspace, props.file.name!);
36 | toast("Deleted file");
37 | } catch (error) {
38 | console.error(error);
39 | }
40 | };
41 |
42 | return (
43 |
44 |
45 | Delete file
46 |
47 | Are you sure you want to delete "{props.file.name}"? This action
48 | cannot be undone.
49 |
50 |
51 |
52 |
53 | {
57 | handleDeleteFile();
58 | }}
59 | >
60 | Confirm
61 |
62 |
63 |
64 | Cancel
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | export default DeleteFile;
72 |
--------------------------------------------------------------------------------
/app/src/components/file/fileItem.tsx:
--------------------------------------------------------------------------------
1 | import type { FileEntry } from "@tauri-apps/api/fs";
2 | import { appWindow } from "@tauri-apps/api/window";
3 |
4 | import { useState } from "react";
5 | import { cn } from "@typethings/ui";
6 | import { BookOpen, Trash } from "lucide-react";
7 | import { useLocation, useNavigate } from "react-router-dom";
8 |
9 | import { readFile, getFileNameWithoutExtension } from "@typethings/functions";
10 | import DeleteFile from "./deleteFile";
11 |
12 | // From Sidebar (shared classes & icon size):
13 | import {
14 | SidebarItemClasses,
15 | SidebarItemIconSize,
16 | } from "@/components/sidebar/shared";
17 |
18 | import {
19 | Button,
20 | Dialog,
21 | DialogTrigger,
22 | ContextMenu,
23 | ContextMenuContent,
24 | ContextMenuItem,
25 | ContextMenuTrigger,
26 | } from "@typethings/ui";
27 | import { useWorkspaceStore } from "@/store/workspaceStore";
28 |
29 | interface iFileItemProps extends FileEntry {
30 | active?: boolean;
31 | }
32 |
33 | const FileItem = (props: iFileItemProps) => {
34 | const selectedWorkspace = useWorkspaceStore(
35 | (state) => state.selectedWorkspace,
36 | );
37 | const selectFile = useWorkspaceStore((state) => state.setSelectedFile);
38 | const selectedFile = useWorkspaceStore((state) => state.selectedFile);
39 | const [dropdownOpen, setDropdownOpen] = useState(false);
40 | const router = useNavigate();
41 | const location = useLocation();
42 |
43 | const handleOpenFile = async () => {
44 | try {
45 | const file = await readFile({
46 | path: props.path,
47 | });
48 | selectFile({
49 | path: props.path,
50 | content: file,
51 | });
52 | router("/editor");
53 | appWindow.setTitle(
54 | `${getFileNameWithoutExtension(props.name!)} - Typethings`,
55 | );
56 | } catch (error) {
57 | console.error(error);
58 | }
59 | };
60 |
61 | // Why component wrap the component?
62 | // https://ui.shadcn.com/docs/components/dialog#notes
63 | // Change line 105.
64 |
65 | return (
66 |
67 |
68 |
69 |
81 |
82 |
83 |
84 |
85 | {getFileNameWithoutExtension(props.name!)}
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | Open
97 |
98 |
99 |
100 |
101 |
102 |
103 | Delete
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | );
112 | };
113 |
114 | export default FileItem;
115 |
--------------------------------------------------------------------------------
/app/src/components/file/fileList.tsx:
--------------------------------------------------------------------------------
1 | import type { FileEntry } from "@typethings/functions";
2 | import FileItem from "./fileItem";
3 |
4 | interface iFileListProps {
5 | files: FileEntry[];
6 | }
7 |
8 | const FileList = ({ files }: iFileListProps) => {
9 | return (
10 |
11 | {files ? (
12 | files.map((file) => (
13 |
14 | ))
15 | ) : (
16 |
17 |
No markdown files.
18 |
19 | )}
20 |
21 | );
22 | };
23 |
24 | export default FileList;
25 |
--------------------------------------------------------------------------------
/app/src/components/file/openFile.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import { useState } from "react";
3 |
4 | import {
5 | Button,
6 | Dialog,
7 | DialogContent,
8 | DialogHeader,
9 | DialogTitle,
10 | DialogTrigger,
11 | } from "@typethings/ui";
12 |
13 | // import { openFile } from "@/functions/openFile";
14 |
15 | interface iCreateFileProps {
16 | trigger: ReactNode;
17 | }
18 |
19 | const OpenFile = (props: iCreateFileProps) => {
20 | const [openDialog, setOpenDialog] = useState(false);
21 |
22 | const handleOpenFile = async () => {
23 | try {
24 | // const selectedFile = await openFile();
25 | } catch (error) {
26 | console.error(error);
27 | }
28 | };
29 |
30 | return (
31 |
32 | {props.trigger}
33 |
34 |
35 | Open file
36 |
37 |
38 | Open file
39 |
40 |
41 |
42 | );
43 | };
44 |
45 | export default OpenFile;
46 |
--------------------------------------------------------------------------------
/app/src/components/file/renameFile.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | DialogContent,
4 | DialogDescription,
5 | DialogFooter,
6 | DialogHeader,
7 | DialogTitle,
8 | } from "@typethings/ui";
9 |
10 | const RenameFile = () => {
11 |
12 | // Delete function:
13 | // const handleDeleteFile = async () => {
14 | // try {
15 | // const desktopPath = await desktopDir();
16 | // setOpenDialog(false);
17 | // } catch (error) {
18 | // console.error(error);
19 | // }
20 | // };
21 |
22 | return (
23 |
24 |
25 | Rename
26 |
27 | This action cannot be undone. Are you sure you want to permanently
28 | delete this file from our servers?
29 |
30 |
31 |
32 | Confirm
33 |
34 |
35 | );
36 | };
37 |
38 | export default RenameFile;
39 |
--------------------------------------------------------------------------------
/app/src/components/folder/createFolder.tsx:
--------------------------------------------------------------------------------
1 | import { useState, type ReactNode } from "react";
2 | import {
3 | Dialog,
4 | DialogTrigger,
5 | DialogContent,
6 | DialogHeader,
7 | DialogTitle,
8 | DialogDescription,
9 | Input,
10 | FormGroup,
11 | DialogFooter,
12 | Button,
13 | } from "@typethings/ui";
14 | import { SubmitHandler, useForm } from "react-hook-form";
15 | import { createFolder } from "@typethings/functions";
16 | import { useWorkspaceStore } from "@/store/workspaceStore";
17 | import { toast } from "sonner";
18 |
19 | interface iCreateFolderProps {
20 | trigger: ReactNode;
21 | }
22 |
23 | interface iCreateFolderInputs {
24 | title: string;
25 | }
26 |
27 | const CreateFolder = (props: iCreateFolderProps) => {
28 | const { register, handleSubmit } = useForm();
29 | const [open, setOpen] = useState(false);
30 | const workspaces = useWorkspaceStore((state) => state.workspaces);
31 | const addWorkspace = useWorkspaceStore((state) => state.addWorkspace);
32 |
33 | const handleCreateFolder: SubmitHandler = async (
34 | data,
35 | ) => {
36 | try {
37 | if (workspaces.some((workspace) => workspace.folderName === data.title)) {
38 | toast.error(`Folder already exists.`);
39 | return;
40 | }
41 | const create = await createFolder(data.title);
42 | addWorkspace({
43 | folderName: data.title,
44 | folderPath: create?.directory!,
45 | files: [],
46 | createdAt: new Date(),
47 | });
48 | setOpen(false);
49 | toast.success(`Folder created successfully.`, {
50 | description: `Created on ${create?.directory!}.`,
51 | });
52 | } catch (error) {
53 | console.log(error);
54 | toast.success(`An error occurred.`);
55 | }
56 | };
57 |
58 | return (
59 |
60 | {props.trigger}
61 |
62 |
63 | New workspace
64 |
65 | A folder will be created in your system documents folder.
66 |
67 |
68 |
74 |
75 | setOpen(false)}>
76 | Cancel
77 |
78 | Create
79 |
80 |
81 |
82 | );
83 | };
84 |
85 | export default CreateFolder;
86 |
--------------------------------------------------------------------------------
/app/src/components/folder/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState, type ReactNode } from "react";
2 | import {
3 | FolderClosed,
4 | FolderIcon,
5 | FolderOpen,
6 | FolderOpenIcon,
7 | X,
8 | } from "lucide-react";
9 |
10 | import { SidebarItemClasses, SidebarItemIconSize } from "@/components/sidebar/shared";
11 |
12 | import {
13 | cn,
14 | Button,
15 | ContextMenu,
16 | ContextMenuContent,
17 | ContextMenuItem,
18 | ContextMenuTrigger,
19 | Collapsible,
20 | CollapsibleContent,
21 | CollapsibleTrigger,
22 | } from "@typethings/ui";
23 |
24 | import { useWorkspaceStore } from "@/store/workspaceStore";
25 |
26 | interface FolderProps {
27 | name: string;
28 | path: string;
29 | children: ReactNode;
30 | }
31 |
32 | const Folder = (props: FolderProps) => {
33 | const [openCollapsible, setOpenCollapsible] = useState(false);
34 | const [openContextMenu, setOpenContextMenu] = useState(false);
35 | const deleteWorkspace = useWorkspaceStore((state) => state.deleteWorkspace);
36 | const selectWorkspace = useWorkspaceStore((state) => state.selectWorkspace);
37 |
38 | const handleDeleteWorkspace = (path: string) => {
39 | selectWorkspace(null);
40 | deleteWorkspace(path);
41 | };
42 |
43 | return (
44 |
45 |
46 | setOpenCollapsible(!openCollapsible)}
50 | >
51 |
52 |
62 |
63 | {openCollapsible ? (
64 |
65 | ) : (
66 |
67 | )}
68 |
69 | {props.name}
70 |
71 |
72 |
77 | {props.children}
78 |
79 |
80 |
81 |
82 | setOpenCollapsible(!openCollapsible)}>
83 |
84 | {openCollapsible ? (
85 |
86 | ) : (
87 |
88 | )}
89 | {openCollapsible ? "Hide" : "Show"} files
90 |
91 |
92 | handleDeleteWorkspace(props.path)}>
93 |
94 |
95 | Close workspace
96 |
97 |
98 |
99 |
100 | );
101 | };
102 |
103 | export default Folder;
104 |
--------------------------------------------------------------------------------
/app/src/components/monaco/index.tsx:
--------------------------------------------------------------------------------
1 | import type { Monaco, EditorProps } from "@monaco-editor/react";
2 | import { Editor } from "@monaco-editor/react";
3 | import CSTheme from "@/styles/monaco-theme.json";
4 |
5 | const CustomEditor = (props: EditorProps) => {
6 | // Set custom theme:
7 | const handleEditorDidMount = (monaco: Monaco) => {
8 | monaco.editor.defineTheme("customTheme", {
9 | base: "vs-dark",
10 | inherit: true,
11 | ...CSTheme,
12 | });
13 | };
14 |
15 | return (
16 |
41 | );
42 | };
43 |
44 | export default CustomEditor;
45 |
--------------------------------------------------------------------------------
/app/src/components/pageNavbar/index.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import { cn, buttonVariants, Button } from "@typethings/ui";
3 | import { SidebarClose, SidebarOpen, X } from "lucide-react";
4 | import { Link } from "react-router-dom";
5 | import { useAppStore } from "@/store/appStore";
6 |
7 | interface PageNavbarProps {
8 | title: string;
9 | border?: boolean;
10 | close?: boolean;
11 | children?: ReactNode;
12 | }
13 |
14 | const PageNavbar = (props: PageNavbarProps) => {
15 | const toggleDrawer = useAppStore((state) => state.toggleDrawer);
16 | const openDrawer = useAppStore((state) => state.openDrawer);
17 | return (
18 |
19 |
27 |
28 |
toggleDrawer()}
37 | >
38 | {openDrawer ? (
39 |
40 | ) : (
41 |
42 | )}
43 |
44 |
45 | {props.title}
46 |
47 |
48 | {props.close && (
49 |
57 |
58 |
59 | )}
60 |
61 | {props.children}
62 |
63 | );
64 | };
65 |
66 | export default PageNavbar;
67 |
--------------------------------------------------------------------------------
/app/src/components/providers/index.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from "@/providers/themeProvider";
2 | import { Toaster } from "sonner";
3 |
4 | const Providers = () => {
5 | const { theme } = useTheme();
6 | return (
7 | <>
8 |
19 | >
20 | );
21 | };
22 |
23 | export default Providers;
24 |
--------------------------------------------------------------------------------
/app/src/components/settings/account.tsx:
--------------------------------------------------------------------------------
1 | import { useUserStore } from "@/store/userStore";
2 | import { useForm, SubmitHandler } from "react-hook-form";
3 |
4 | import SettingsGroup from "./settingsGroup";
5 | import { toast } from "sonner";
6 | import Avatar from "boring-avatars";
7 | import { Alert, Button, Input } from "@typethings/ui";
8 | import { User } from "lucide-react";
9 |
10 | interface iUpdateUserInputs {
11 | username: string;
12 | }
13 |
14 | const Account = () => {
15 | const user = useUserStore((state) => state.user);
16 | const {
17 | register,
18 | handleSubmit,
19 | formState: { errors },
20 | } = useForm();
21 |
22 | const handleSaveUser: SubmitHandler = (data) => {
23 | useUserStore.setState({ user: data.username });
24 | toast.success("User saved");
25 | };
26 |
27 | return (
28 | <>
29 | }>
30 |
67 |
68 | >
69 | );
70 | };
71 |
72 | export default Account;
73 |
--------------------------------------------------------------------------------
/app/src/components/settings/appearance.tsx:
--------------------------------------------------------------------------------
1 | import { Check, Monitor, Moon, Paintbrush, Sun } from "lucide-react";
2 | import SettingsGroup from "./settingsGroup";
3 | import { useTheme } from "@/providers/themeProvider";
4 | import { Button } from "@typethings/ui";
5 |
6 | const Appearance = () => {
7 | const { theme, setTheme } = useTheme();
8 | const iconSize = 24;
9 |
10 | const themes = [
11 | {
12 | theme: "light",
13 | icon: ,
14 | action: () => setTheme("light"),
15 | },
16 | {
17 | theme: "dark",
18 | icon: ,
19 | action: () => setTheme("dark"),
20 | },
21 | {
22 | theme: "system",
23 | icon: ,
24 | action: () => setTheme("system"),
25 | },
26 | ];
27 |
28 | return (
29 | <>
30 | }>
31 |
32 | {themes.map((th) => (
33 |
40 | {th.icon}
41 |
42 | {th.theme.charAt(0).toUpperCase() + th.theme.slice(1)}
43 |
44 | {theme === th.theme && (
45 |
46 | )}
47 |
48 | ))}
49 |
50 |
51 | >
52 | );
53 | };
54 |
55 | export default Appearance;
56 |
--------------------------------------------------------------------------------
/app/src/components/settings/settingsGroup.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import { cn } from "@typethings/ui";
3 |
4 | interface SettingsGroupProps {
5 | title: string;
6 | children: ReactNode;
7 | icon?: ReactNode;
8 | className?: string;
9 | }
10 |
11 | const SettingsGroup = (props: SettingsGroupProps) => {
12 | return (
13 |
14 |
15 | {props.icon && props.icon}
16 |
{props.title}
17 |
18 | {props.children}
19 |
20 | );
21 | };
22 |
23 | export default SettingsGroup;
24 |
--------------------------------------------------------------------------------
/app/src/components/settings/userSettings.tsx:
--------------------------------------------------------------------------------
1 | import { buttonVariants, cn } from "@typethings/ui";
2 | import { useUserStore } from "@/store/userStore";
3 | import { Settings } from "lucide-react";
4 | import {
5 | SidebarItemClasses,
6 | SidebarItemIconSize,
7 | SidebarLinkActiveClasses,
8 | } from "../sidebar/shared";
9 | import { Link, useLocation } from "react-router-dom";
10 | import { appWindow } from "@tauri-apps/api/window";
11 | import Avatar from "boring-avatars";
12 |
13 | const UserSettings = () => {
14 | const user = useUserStore((state) => state.user);
15 | const route = useLocation();
16 | return (
17 | {
20 | appWindow.setTitle(`Inbox - Typethings`);
21 | }}
22 | className={cn(
23 | buttonVariants({ variant: "ghost" }),
24 | SidebarItemClasses,
25 | route.pathname === "/settings" ? SidebarLinkActiveClasses : "",
26 | )}
27 | >
28 |
38 |
39 | );
40 | };
41 |
42 | export default UserSettings;
43 |
--------------------------------------------------------------------------------
/app/src/components/sidebar/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import { appWindow } from "@tauri-apps/api/window";
3 | import { cn, Button, buttonVariants } from "@typethings/ui";
4 | import { Link, Outlet, useLocation } from "react-router-dom";
5 | import { Plus, Folders, Inbox } from "lucide-react";
6 | import { useAppStore } from "@/store/appStore";
7 |
8 | import Search from "@/components/search";
9 | import SidebarGroup from "@/components/sidebar/sidebarGroup";
10 | import Explorer from "@/components/explorer";
11 | import CreateFile from "@/components/file/createFile";
12 | import ManageWorkspaces from "@/components/workspaces/manageWorkspaces";
13 | import UserSettings from "@/components/settings/userSettings";
14 |
15 | import {
16 | SidebarItemClasses,
17 | SidebarItemIconSize,
18 | SidebarLinkActiveClasses,
19 | } from "./shared";
20 |
21 | // Sidebar Config:
22 | const [minWidth, maxWidth, defaultWidth] = [200, 300, 208];
23 |
24 | const Sidebar = () => {
25 | const [width, setWidth] = useState(defaultWidth);
26 | const isResized = useRef(false);
27 | const openDrawer = useAppStore((state) => state.openDrawer);
28 | const route = useLocation();
29 |
30 | // Resize sidebar:
31 | useEffect(() => {
32 | window.addEventListener("mousemove", (e) => {
33 | if (!isResized.current) {
34 | return;
35 | }
36 | setWidth((previousWidth) => {
37 | const newWidth = previousWidth + e.movementX / 2;
38 | const isWidthInRange = newWidth >= minWidth && newWidth <= maxWidth;
39 | return isWidthInRange ? newWidth : previousWidth;
40 | });
41 | });
42 |
43 | window.addEventListener("mouseup", () => {
44 | isResized.current = false;
45 | });
46 | }, []);
47 |
48 | return (
49 |
50 |
61 |
62 |
63 | {
66 | appWindow.setTitle(`Inbox - Typethings`);
67 | }}
68 | className={cn(
69 | buttonVariants({ variant: "ghost" }),
70 | SidebarItemClasses,
71 | route.pathname === "/" ? SidebarLinkActiveClasses : "",
72 | )}
73 | >
74 |
75 |
76 | Inbox
77 |
78 |
79 |
82 |
86 |
87 | }
88 | />
89 |
92 |
93 |
94 | Workspaces
95 |
96 |
97 | }
98 | />
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | {
115 | isResized.current = true;
116 | }}
117 | />
118 |
119 |
126 |
127 |
128 |
129 | );
130 | };
131 |
132 | export default Sidebar;
133 |
--------------------------------------------------------------------------------
/app/src/components/sidebar/shared.ts:
--------------------------------------------------------------------------------
1 | import { cn } from "@typethings/ui";
2 |
3 | export const SidebarItemClasses = cn("w-full justify-start text-sm px-2 group");
4 | export const SidebarLinkActiveClasses = cn(
5 | "bg-neutral-300/60 dark:bg-neutral-700/40",
6 | );
7 | export const SidebarItemIconSize = 16;
8 |
--------------------------------------------------------------------------------
/app/src/components/sidebar/sidebarGroup.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import { cn } from "@typethings/ui";
3 |
4 | interface SidebarGroupProps {
5 | border?: boolean;
6 | title?: string;
7 | children: ReactNode;
8 | }
9 |
10 | const SidebarGroup = (props: SidebarGroupProps) => {
11 | return (
12 |
13 | {props.title && (
14 |
15 | {props.title}
16 |
17 | )}
18 | {props.children}
19 |
20 | );
21 | };
22 |
23 | export default SidebarGroup;
24 |
--------------------------------------------------------------------------------
/app/src/components/tip.tsx:
--------------------------------------------------------------------------------
1 | import { Lightbulb } from "lucide-react";
2 | import {
3 | Tooltip,
4 | TooltipContent,
5 | TooltipProvider,
6 | TooltipTrigger,
7 | } from "@typethings/ui";
8 |
9 | interface TipProps {
10 | text: string;
11 | iconSize?: number;
12 | }
13 |
14 | const Tip = (props: TipProps) => {
15 | return (
16 |
17 |
18 |
22 |
23 |
24 |
30 | {props.text}
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default Tip;
38 |
--------------------------------------------------------------------------------
/app/src/components/workspaces/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { FolderIcon, FolderOpen, FolderPlus, Search, X } from "lucide-react";
3 | import { toast } from "sonner";
4 |
5 | import { useWorkspaceStore } from "@/store/workspaceStore";
6 | import {
7 | checkDirFile,
8 | readFilesFromFolder,
9 | selectFolder,
10 | } from "@typethings/functions";
11 |
12 | import {
13 | Button,
14 | Collapsible,
15 | CollapsibleContent,
16 | CollapsibleTrigger,
17 | Input,
18 | RadioGroup,
19 | RadioGroupItem,
20 | Tooltip,
21 | TooltipContent,
22 | TooltipProvider,
23 | TooltipTrigger,
24 | } from "@typethings/ui";
25 | import CreateFolder from "../folder/createFolder";
26 |
27 | interface WorkspacesProps {
28 | checkOption?: boolean;
29 | }
30 |
31 | const Workspaces = (props: WorkspacesProps) => {
32 | const workspaces = useWorkspaceStore((state) => state.workspaces);
33 | const addWorkspace = useWorkspaceStore((state) => state.addWorkspace);
34 | const deleteWorkspace = useWorkspaceStore((state) => state.deleteWorkspace);
35 | const selectWorkspace = useWorkspaceStore((state) => state.selectWorkspace);
36 | const selectedWorkspace = useWorkspaceStore(
37 | (state) => state.selectedWorkspace,
38 | );
39 | const [search, setSearch] = useState
("");
40 | const [openCollapsible, setOpenCollapsible] = useState(false);
41 |
42 | const handleAddWorkspace = async () => {
43 | try {
44 | const folder = await selectFolder();
45 | if (!folder) {
46 | return;
47 | }
48 | // Check if folder is already added:
49 | if (
50 | workspaces
51 | .map((workspace) => workspace.folderPath)
52 | .includes(folder?.folderPath)
53 | ) {
54 | toast.error("The workspace already exists.", {
55 | description: `${folder.folderName} is already added as a workspace.`,
56 | });
57 | return;
58 | }
59 | // Get files from folder:
60 | const result = await readFilesFromFolder({
61 | path: folder.folderPath,
62 | });
63 | // Add folder to workspaces:
64 | addWorkspace({
65 | folderName: folder.folderName,
66 | folderPath: folder.folderPath,
67 | files: result!,
68 | createdAt: new Date(),
69 | });
70 | // Select workspace:
71 | selectWorkspace(folder.folderPath);
72 | // Show toast:
73 | toast.success(`Workspace added.`, {
74 | description: `You can now start working on ${folder.folderName}.`,
75 | });
76 | } catch (error) {
77 | toast.error("Something went wrong.");
78 | }
79 | };
80 |
81 | const handleDeleteWorkspace = (path: string) => {
82 | selectWorkspace(null);
83 | deleteWorkspace(path);
84 | };
85 |
86 | const handleSelectWorkspace = async (path: string) => {
87 | try {
88 | const check = await checkDirFile(path);
89 | if (!check) {
90 | toast.error("Directory not found.", {
91 | description: `The directory ${path} was not found.`,
92 | });
93 | deleteWorkspace(path);
94 | return;
95 | }
96 | selectWorkspace(path);
97 | } catch (error) {
98 | toast.error("Something went wrong.");
99 | }
100 | };
101 |
102 | return (
103 |
104 |
105 |
106 |
112 |
113 | New
114 |
115 | }
116 | />
117 | {
121 | e.preventDefault();
122 | handleAddWorkspace();
123 | }}
124 | >
125 |
126 | Open
127 |
128 |
129 |
130 | {openCollapsible ? : }
131 |
132 |
133 |
134 |
135 | setSearch(e.target.value)}
140 | />
141 |
142 |
143 |
144 | {workspaces.length > 0 ? (
145 | workspaces
146 | .sort()
147 | .filter(
148 | search
149 | ? (workspace) =>
150 | workspace.folderName
151 | .toLowerCase()
152 | .includes(search.toLowerCase())
153 | : () => true,
154 | )
155 | .map((workspace) => (
156 |
160 |
161 |
162 | {props.checkOption ? (
163 |
167 | handleSelectWorkspace(workspace.folderPath)
168 | }
169 | checked={
170 | workspace.folderPath === selectedWorkspace?.folderPath
171 | }
172 | />
173 | ) : (
174 |
175 | )}
176 |
177 |
182 | {workspace.folderName}
183 |
184 |
185 |
186 |
187 |
188 |
192 | handleDeleteWorkspace(workspace.folderPath)
193 | }
194 | >
195 |
196 |
197 |
198 |
199 | Close workspace
200 |
201 |
202 |
203 |
204 | ))
205 | ) : (
206 |
207 |
You don't have any workspaces yet.
208 |
209 | )}
210 |
211 |
212 | );
213 | };
214 |
215 | export default Workspaces;
216 |
--------------------------------------------------------------------------------
/app/src/components/workspaces/manageWorkspaces.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 |
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogDescription,
7 | DialogHeader,
8 | DialogTitle,
9 | DialogTrigger,
10 | } from "@typethings/ui";
11 |
12 | import Workspaces from "@/components/workspaces";
13 |
14 | interface ManageWorkspacesProps {
15 | trigger: ReactNode;
16 | }
17 |
18 | const ManageWorkspaces = (props: ManageWorkspacesProps) => {
19 | return (
20 |
21 | {props.trigger}
22 |
23 |
24 | Manage workspaces
25 |
26 | A workspace is a system folder. Here you can add and remove
27 | workspaces. Close a workspace does not delete the folder.
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default ManageWorkspaces;
37 |
--------------------------------------------------------------------------------
/app/src/data/fileExtensions.ts:
--------------------------------------------------------------------------------
1 | export const fileExtensions = [
2 | { name: "plaintext", extension: "txt" },
3 | { name: "abap", extension: "abap" },
4 | { name: "apex", extension: "cls" },
5 | { name: "azcli", extension: "azcli" },
6 | { name: "bat", extension: "bat" },
7 | { name: "bicep", extension: "bicep" },
8 | { name: "cameligo", extension: "ligo" },
9 | { name: "clojure", extension: "clj" },
10 | { name: "coffeescript", extension: "coffee" },
11 | { name: "c", extension: "c" },
12 | { name: "cpp", extension: "cpp" },
13 | { name: "csharp", extension: "cs" },
14 | { name: "csp", extension: "csp" },
15 | { name: "css", extension: "css" },
16 | { name: "cypher", extension: "cyp" },
17 | { name: "dart", extension: "dart" },
18 | { name: "dockerfile", extension: "dockerfile" },
19 | { name: "ecl", extension: "ecl" },
20 | { name: "elixir", extension: "ex" },
21 | { name: "flow9", extension: "f9" },
22 | { name: "fsharp", extension: "fs" },
23 | { name: "freemarker2", extension: "ftl" },
24 | { name: "go", extension: "go" },
25 | { name: "graphql", extension: "graphql" },
26 | { name: "handlebars", extension: "hbs" },
27 | { name: "hcl", extension: "hcl" },
28 | { name: "html", extension: "html" },
29 | { name: "ini", extension: "ini" },
30 | { name: "java", extension: "java" },
31 | { name: "javascript", extension: "js" },
32 | { name: "typescript", extension: "ts" },
33 | { name: "julia", extension: "jl" },
34 | { name: "kotlin", extension: "kt" },
35 | { name: "less", extension: "less" },
36 | { name: "lexon", extension: "lexon" },
37 | { name: "lua", extension: "lua" },
38 | { name: "liquid", extension: "liquid" },
39 | { name: "m3", extension: "m3" },
40 | { name: "markdown", extension: "md" },
41 | { name: "mips", extension: "mips" },
42 | { name: "msdax", extension: "dax" },
43 | { name: "mysql", extension: "sql" },
44 | { name: "objective-c", extension: "m" },
45 | { name: "pascal", extension: "pas" },
46 | { name: "pascaligo", extension: "pasa" },
47 | { name: "perl", extension: "pl" },
48 | { name: "pgsql", extension: "pgsql" },
49 | { name: "php", extension: "php" },
50 | { name: "pla", extension: "pla" },
51 | { name: "postiats", extension: "dats" },
52 | { name: "powerquery", extension: "pq" },
53 | { name: "powershell", extension: "ps1" },
54 | { name: "proto", extension: "proto" },
55 | { name: "pug", extension: "pug" },
56 | { name: "python", extension: "py" },
57 | { name: "qsharp", extension: "qs" },
58 | { name: "r", extension: "r" },
59 | { name: "razor", extension: "cshtml" },
60 | { name: "redis", extension: "redis" },
61 | { name: "redshift", extension: "redshift" },
62 | { name: "restructuredtext", extension: "rst" },
63 | { name: "ruby", extension: "rb" },
64 | { name: "rust", extension: "rs" },
65 | { name: "sb", extension: "sb" },
66 | { name: "scala", extension: "scala" },
67 | { name: "scheme", extension: "scm" },
68 | { name: "scss", extension: "scss" },
69 | { name: "shell", extension: "sh" },
70 | { name: "sol", extension: "sol" },
71 | { name: "aes", extension: "aes" },
72 | { name: "sparql", extension: "sparql" },
73 | { name: "sql", extension: "sql" },
74 | ];
75 |
76 | export const fileExtensionsByCategory = {
77 | Text: [
78 | { name: "plaintext", extension: "txt" },
79 | { name: "markdown", extension: "md" },
80 | { name: "restructuredtext", extension: "rst" },
81 | ],
82 | "Programming Languages": [
83 | { name: "abap", extension: "abap" },
84 | { name: "apex", extension: "cls" },
85 | { name: "azcli", extension: "azcli" },
86 | { name: "bicep", extension: "bicep" },
87 | { name: "cameligo", extension: "ligo" },
88 | { name: "clojure", extension: "clj" },
89 | { name: "coffeescript", extension: "coffee" },
90 | { name: "c", extension: "c" },
91 | { name: "cpp", extension: "cpp" },
92 | { name: "csharp", extension: "cs" },
93 | { name: "csp", extension: "csp" },
94 | { name: "dart", extension: "dart" },
95 | { name: "ecl", extension: "ecl" },
96 | { name: "elixir", extension: "ex" },
97 | { name: "flow9", extension: "f9" },
98 | { name: "fsharp", extension: "fs" },
99 | { name: "go", extension: "go" },
100 | { name: "graphql", extension: "graphql" },
101 | { name: "java", extension: "java" },
102 | { name: "javascript", extension: "js" },
103 | { name: "julia", extension: "jl" },
104 | { name: "kotlin", extension: "kt" },
105 | { name: "lua", extension: "lua" },
106 | { name: "m3", extension: "m3" },
107 | { name: "mips", extension: "mips" },
108 | { name: "msdax", extension: "dax" },
109 | { name: "objective-c", extension: "m" },
110 | { name: "pascal", extension: "pas" },
111 | { name: "pascaligo", extension: "pasa" },
112 | { name: "perl", extension: "pl" },
113 | { name: "pgsql", extension: "pgsql" },
114 | { name: "php", extension: "php" },
115 | { name: "postiats", extension: "dats" },
116 | { name: "powerquery", extension: "pq" },
117 | { name: "powershell", extension: "ps1" },
118 | { name: "proto", extension: "proto" },
119 | { name: "python", extension: "py" },
120 | { name: "qsharp", extension: "qs" },
121 | { name: "r", extension: "r" },
122 | { name: "ruby", extension: "rb" },
123 | { name: "rust", extension: "rs" },
124 | { name: "sb", extension: "sb" },
125 | { name: "scala", extension: "scala" },
126 | { name: "scheme", extension: "scm" },
127 | { name: "sql", extension: "sql" },
128 | ],
129 | "Web Development": [
130 | { name: "css", extension: "css" },
131 | { name: "html", extension: "html" },
132 | { name: "handlebars", extension: "hbs" },
133 | { name: "less", extension: "less" },
134 | { name: "scss", extension: "scss" },
135 | { name: "razor", extension: "cshtml" },
136 | ],
137 | Scripting: [
138 | { name: "bat", extension: "bat" },
139 | { name: "cypher", extension: "cyp" },
140 | { name: "lua", extension: "lua" },
141 | { name: "liquid", extension: "liquid" },
142 | { name: "shell", extension: "sh" },
143 | ],
144 | Database: [
145 | { name: "mysql", extension: "sql" },
146 | { name: "redis", extension: "redis" },
147 | { name: "redshift", extension: "redshift" },
148 | ],
149 | Other: [
150 | { name: "dockerfile", extension: "dockerfile" },
151 | { name: "ini", extension: "ini" },
152 | { name: "aes", extension: "aes" },
153 | { name: "sparql", extension: "sparql" },
154 | ],
155 | };
156 |
--------------------------------------------------------------------------------
/app/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import { createBrowserRouter, RouterProvider } from "react-router-dom";
4 |
5 | // App styles:
6 | import "@/styles/globals.css";
7 | import "@typethings/ui/dist/index.css";
8 |
9 | // Global Imports:
10 | import Sidebar from "./components/sidebar";
11 |
12 | // Routes:
13 | import App from "./routes";
14 | import Settings from "./routes/settings";
15 | import Editor from "./routes/editor";
16 |
17 | // When the app is in production mode, we use the custom error boundary component:
18 | import ErrorElement from "./components/errorElement";
19 |
20 | // Providers:
21 | import { ThemeProvider } from "./providers/themeProvider";
22 | import Providers from "./components/providers";
23 | import { HotkeysProvider } from "react-hotkeys-hook";
24 |
25 | // Router:
26 | const router = createBrowserRouter([
27 | {
28 | element: ,
29 | errorElement: ,
30 | children: [
31 | {
32 | index: true,
33 | element: ,
34 | },
35 | {
36 | path: "/settings",
37 | element: ,
38 | },
39 | {
40 | path: "/editor",
41 | element: ,
42 | },
43 | ],
44 | },
45 | ]);
46 |
47 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | ,
56 | );
57 |
--------------------------------------------------------------------------------
/app/src/providers/themeProvider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from "react";
2 |
3 | type Theme = "dark" | "light" | "system";
4 |
5 | type ThemeProviderProps = {
6 | children: React.ReactNode;
7 | defaultTheme?: Theme;
8 | storageKey?: string;
9 | };
10 |
11 | type ThemeProviderState = {
12 | theme: Theme;
13 | setTheme: (theme: Theme) => void;
14 | };
15 |
16 | const initialState: ThemeProviderState = {
17 | theme: "system",
18 | setTheme: () => null,
19 | };
20 |
21 | const ThemeProviderContext = createContext(initialState);
22 |
23 | export function ThemeProvider({
24 | children,
25 | defaultTheme = "system",
26 | storageKey = "typethings-theme",
27 | ...props
28 | }: ThemeProviderProps) {
29 | const [theme, setTheme] = useState(
30 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
31 | );
32 |
33 | useEffect(() => {
34 | const root = window.document.documentElement;
35 |
36 | root.classList.remove("light", "dark");
37 |
38 | if (theme === "system") {
39 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
40 | .matches
41 | ? "dark"
42 | : "light";
43 |
44 | root.classList.add(systemTheme);
45 | return;
46 | }
47 |
48 | root.classList.add(theme);
49 | }, [theme]);
50 |
51 | const value = {
52 | theme,
53 | setTheme: (theme: Theme) => {
54 | localStorage.setItem(storageKey, theme);
55 | setTheme(theme);
56 | },
57 | };
58 |
59 | return (
60 |
61 | {children}
62 |
63 | );
64 | }
65 |
66 | export const useTheme = () => {
67 | const context = useContext(ThemeProviderContext);
68 |
69 | if (context === undefined)
70 | throw new Error("useTheme must be used within a ThemeProvider");
71 |
72 | return context;
73 | };
--------------------------------------------------------------------------------
/app/src/routes/editor.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useHotkeys } from "react-hotkeys-hook";
3 | import { toast } from "sonner";
4 | import { useWorkspaceStore } from "@/store/workspaceStore";
5 | import "@/styles/tiptap-code.css";
6 |
7 | import { getFileNameWithoutExtension, updateFile } from "@typethings/functions";
8 | import { useEditor, Menu, Editor, Extensions } from "@typethings/editor";
9 |
10 | import PageNavbar from "@/components/pageNavbar";
11 |
12 | import {
13 | cn,
14 | buttonVariants,
15 | TooltipStyles,
16 | ProseClasses,
17 | } from "@typethings/ui";
18 |
19 | const ProseStyle = cn(
20 | "focus:outline-none outline-none",
21 | "overflow-y-auto overflow-x-hidden mx-auto",
22 | ProseClasses,
23 | );
24 |
25 | const EditorPage = () => {
26 | const fileSelected = useWorkspaceStore((state) => state.selectedFile);
27 | const [text, setText] = useState("");
28 | const editor = useEditor({
29 | extensions: Extensions,
30 | injectCSS: false,
31 | content: fileSelected?.content,
32 | onUpdate: ({ editor }) => {
33 | setText(editor.storage.markdown.getMarkdown());
34 | },
35 | autofocus: true,
36 | editable: true,
37 | editorProps: {
38 | attributes: {
39 | class: ProseStyle,
40 | },
41 | },
42 | });
43 |
44 | useHotkeys("ctrl+s", () => handleSaveFile());
45 |
46 | useEffect(() => {
47 | if (!fileSelected) return;
48 | editor?.chain().focus().setContent(fileSelected.content).run();
49 | setText(fileSelected.content);
50 | }, [fileSelected]);
51 |
52 | if (!fileSelected) return null;
53 |
54 | const handleSaveFile = async () => {
55 | try {
56 | await updateFile({
57 | path: fileSelected.path,
58 | content: text!,
59 | });
60 | toast.success("File saved!");
61 | } catch (error) {
62 | console.error(error);
63 | }
64 | };
65 |
66 | return (
67 | {
74 | setText(editor.storage.markdown.getMarkdown());
75 | }}
76 | >
77 |
81 |
94 |
95 |
96 | );
97 | };
98 |
99 | export default EditorPage;
100 |
--------------------------------------------------------------------------------
/app/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@/components/container";
2 | import PageNavbar from "@/components/pageNavbar";
3 |
4 | function App() {
5 | return (
6 |
7 |
8 | hello ✨
9 |
10 |
11 | );
12 | }
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/app/src/routes/settings.tsx:
--------------------------------------------------------------------------------
1 | import Container from "@/components/container";
2 | import PageNavbar from "@/components/pageNavbar";
3 | import Account from "@/components/settings/account";
4 | import Appearance from "@/components/settings/appearance";
5 |
6 | const Settings = () => {
7 | return (
8 | <>
9 |
10 |
11 |
12 |
13 |
14 | >
15 | );
16 | };
17 |
18 | export default Settings;
19 |
--------------------------------------------------------------------------------
/app/src/store/appStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | interface iAppStore {
4 | openDrawer: boolean;
5 | toggleDrawer: () => void;
6 | }
7 |
8 | export const useAppStore = create((set) => ({
9 | openDrawer: true,
10 | toggleDrawer: () => set((state) => ({ openDrawer: !state.openDrawer })),
11 | }));
12 |
--------------------------------------------------------------------------------
/app/src/store/userStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 | import { devtools, persist } from "zustand/middleware";
3 |
4 | interface iUserStore {
5 | user: string;
6 | setUser: (user: string) => void;
7 | }
8 |
9 | export const useUserStore = create()(
10 | devtools(
11 | persist(
12 | (set) => ({
13 | user: "Typethings",
14 | setUser: (user) => set({ user }),
15 | }),
16 | {
17 | name: "user-storage",
18 | },
19 | ),
20 | ),
21 | );
22 |
--------------------------------------------------------------------------------
/app/src/store/workspaceStore.ts:
--------------------------------------------------------------------------------
1 | import { FileEntry } from "@typethings/functions";
2 | import { create } from "zustand";
3 | import { devtools, persist } from "zustand/middleware";
4 |
5 | export interface workspace {
6 | folderName: string;
7 | folderPath: string;
8 | files: FileEntry[];
9 | createdAt: Date;
10 | }
11 |
12 | interface SelectFile {
13 | path: string;
14 | content: string;
15 | }
16 |
17 | interface iWorkspaceStore {
18 | workspaces: workspace[];
19 | selectedWorkspace: workspace | null;
20 | selectWorkspace: (folderPath: string | null) => void;
21 | addWorkspace: (workspace: workspace) => void;
22 | deleteWorkspace: (folderPath: string) => void;
23 | selectedFile: SelectFile | null;
24 | setSelectedFile: ({ path, content }: SelectFile) => void | null;
25 | addFileToWorkspace: (folderPath: string, file: FileEntry) => void;
26 | deleteFileFromWorkspace: (workspace: string, fileName: string) => void;
27 | }
28 |
29 | export const useWorkspaceStore = create()(
30 | devtools(
31 | persist(
32 | (set) => ({
33 | workspaces: [],
34 | selectedFile: null,
35 | selectedWorkspace: null,
36 | selectWorkspace: (folderPath) =>
37 | set((state) => ({
38 | ...state,
39 | selectedWorkspace: state.workspaces.find(
40 | (w) => w.folderPath === folderPath,
41 | ),
42 | })),
43 | addWorkspace: (workspace) =>
44 | set((state) => ({
45 | ...state,
46 | workspaces: [...state.workspaces, workspace],
47 | })),
48 | deleteWorkspace: (folderPath) =>
49 | set((state) => ({
50 | ...state,
51 | workspaces: state.workspaces.filter(
52 | (w) => w.folderPath !== folderPath,
53 | ),
54 | })),
55 | setSelectedFile: (file) =>
56 | set({
57 | selectedFile: {
58 | path: file.path,
59 | content: file.content,
60 | },
61 | }),
62 | addFileToWorkspace: (folderPath, file) =>
63 | set((state) => ({
64 | ...state,
65 | workspaces: state.workspaces.map((w) =>
66 | w.folderPath === folderPath
67 | ? { ...w, files: [...w.files, file] }
68 | : w,
69 | ),
70 | })),
71 | deleteFileFromWorkspace: (workspace, fileName) =>
72 | set((state) => ({
73 | ...state,
74 | workspaces: state.workspaces.map((w) =>
75 | w.folderPath === workspace
76 | ? {
77 | ...w,
78 | files: w.files.filter((f) => f.name !== fileName),
79 | }
80 | : w,
81 | ),
82 | })),
83 | }),
84 | {
85 | name: "workspace-store",
86 | },
87 | ),
88 | ),
89 | );
90 |
--------------------------------------------------------------------------------
/app/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 0 0% 3.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 0 0% 3.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 0 0% 3.9%;
15 |
16 | --primary: 0 0% 9%;
17 | --primary-foreground: 0 0% 98%;
18 |
19 | --secondary: 0 0% 96.1%;
20 | --secondary-foreground: 0 0% 9%;
21 |
22 | --muted: 0 0% 96.1%;
23 | --muted-foreground: 0 0% 45.1%;
24 |
25 | --accent: 0 0% 96.1%;
26 | --accent-foreground: 0 0% 9%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 0 0% 98%;
30 |
31 | --border: 0 0% 89.8%;
32 | --input: 0 0% 89.8%;
33 | --ring: 0 0% 3.9%;
34 |
35 | --radius: 0.5rem;
36 |
37 | --sb-track-color: rgb(229 229 229 / 0.5);
38 | --sb-thumb-color: #d4d4d4;
39 | --sb-size: 7px;
40 |
41 | --code-bg: #e5e5e5;
42 | }
43 |
44 | .dark {
45 | --background: 0 0% 3.9%;
46 | --foreground: 0 0% 98%;
47 |
48 | --card: 0 0% 3.9%;
49 | --card-foreground: 0 0% 98%;
50 |
51 | --popover: 0 0% 3.9%;
52 | --popover-foreground: 0 0% 98%;
53 |
54 | --primary: 0 0% 98%;
55 | --primary-foreground: 0 0% 9%;
56 |
57 | --secondary: 0 0% 14.9%;
58 | --secondary-foreground: 0 0% 98%;
59 |
60 | --muted: 0 0% 14.9%;
61 | --muted-foreground: 0 0% 63.9%;
62 |
63 | --accent: 0 0% 14.9%;
64 | --accent-foreground: 0 0% 98%;
65 |
66 | --destructive: 0 62.8% 30.6%;
67 | --destructive-foreground: 0 0% 98%;
68 |
69 | --border: 0 0% 14.9%;
70 | --input: 0 0% 14.9%;
71 | --ring: 0 0% 83.1%;
72 |
73 | --sb-track-color: #171717;
74 | --sb-thumb-color: #404040;
75 | --sb-size: 7px;
76 |
77 | --code-bg: #171717;
78 | }
79 | }
80 |
81 | @layer base {
82 | * {
83 | @apply border-border;
84 | }
85 |
86 | body {
87 | @apply bg-background text-foreground;
88 | scrollbar-color: var(--sb-thumb-color) transparent;
89 | }
90 |
91 | body::-webkit-scrollbar {
92 | width: var(--sb-size);
93 | }
94 |
95 | body::-webkit-scrollbar-track {
96 | background: transparent;
97 | }
98 |
99 | body::-webkit-scrollbar-thumb {
100 | background: var(--sb-thumb-color);
101 | border-radius: 10px;
102 | }
103 |
104 | nav::-webkit-scrollbar {
105 | width: var(--sb-size);
106 | }
107 |
108 | nav::-webkit-scrollbar-track {
109 | background: transparent;
110 | }
111 |
112 | nav::-webkit-scrollbar-thumb {
113 | background: var(--sb-thumb-color);
114 | border-radius: 10px;
115 | }
116 |
117 | div::-webkit-scrollbar {
118 | width: var(--sb-size);
119 | height: var(--sb-size);
120 | }
121 |
122 | div::-webkit-scrollbar-track {
123 | background: transparent;
124 | }
125 |
126 | div::-webkit-scrollbar-thumb {
127 | background: var(--sb-thumb-color);
128 | border-radius: 10px;
129 | }
130 |
131 | pre::-webkit-scrollbar {
132 | width: var(--sb-size);
133 | height: var(--sb-size);
134 | }
135 |
136 | pre::-webkit-scrollbar-track {
137 | background: transparent;
138 | }
139 |
140 | pre::-webkit-scrollbar-thumb {
141 | background: var(--sb-thumb-color);
142 | border-radius: 10px;
143 | }
144 | }
145 |
146 | @font-face {
147 | font-family: "Inter-Roman";
148 | src: url("/fonts/Inter-roman.var.woff2") format("woff2");
149 | font-weight: 100 900;
150 | font-display: swap;
151 | font-style: normal;
152 | }
153 |
154 | @font-face {
155 | font-family: "Jetbrains-Mono";
156 | src: url("/fonts/JetBrainsMono-Regular.woff2") format("woff2");
157 | font-weight: 100 900;
158 | font-display: swap;
159 | font-style: normal;
160 | }
161 |
--------------------------------------------------------------------------------
/app/src/styles/tiptap-code.css:
--------------------------------------------------------------------------------
1 | .tiptap {
2 | pre {
3 |
4 | border-radius: 0.3rem;
5 | font-family: "JetBrains-Mono", monospace;
6 | padding: 0.75rem 1rem;
7 | color: currentColor;
8 |
9 | code {
10 | background: none;
11 | color: inherit;
12 | font-size: 14px;
13 | padding: 0;
14 | }
15 |
16 | .hljs-comment,
17 | .hljs-quote {
18 | color: #616161;
19 | }
20 |
21 | .hljs-variable,
22 | .hljs-template-variable,
23 | .hljs-attribute,
24 | .hljs-tag,
25 | .hljs-name,
26 | .hljs-regexp,
27 | .hljs-link,
28 | .hljs-name,
29 | .hljs-selector-id,
30 | .hljs-selector-class {
31 | color: #f43f5e;
32 | }
33 |
34 | .hljs-number,
35 | .hljs-meta,
36 | .hljs-built_in,
37 | .hljs-builtin-name,
38 | .hljs-literal,
39 | .hljs-type,
40 | .hljs-params {
41 | color: #ec4899;
42 | }
43 |
44 | .hljs-string,
45 | .hljs-symbol,
46 | .hljs-bullet {
47 | color: #14b8a6;
48 | }
49 |
50 | .hljs-title,
51 | .hljs-section {
52 | color: #6366f1;
53 | }
54 |
55 | .hljs-keyword,
56 | .hljs-selector-tag {
57 | color: #3b82f6;
58 | }
59 |
60 | .hljs-emphasis {
61 | font-style: italic;
62 | }
63 |
64 | .hljs-strong {
65 | font-weight: 900;
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/app/src/utils/openLink.ts:
--------------------------------------------------------------------------------
1 | export const openLink = (url: string) => {
2 | window.open(url, "_blank");
3 | };
--------------------------------------------------------------------------------
/app/src/utils/text.ts:
--------------------------------------------------------------------------------
1 | // Count words in a string:
2 | export const countWords = (str?: string) => {
3 | return str?.split(/\s+/).filter((word) => word !== "").length;
4 | };
5 |
6 | // Count characters in a string:
7 | export const countCharacters = (str?: string) => {
8 | return str?.length;
9 | };
10 |
--------------------------------------------------------------------------------
/app/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/app/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import sharedConfig from "@typethings/tailwind-config/tailwind.config";
2 | import type { Config } from "tailwindcss";
3 |
4 | const config: Pick = {
5 | presets: [
6 | {
7 | ...sharedConfig,
8 | content: [
9 | "./index.html",
10 | "./src/**/*.{ts,tsx}",
11 | "../packages/ui/src/**/*{.js,.ts,.jsx,.tsx}",
12 | ],
13 | },
14 | ],
15 | };
16 |
17 | export default config;
18 |
--------------------------------------------------------------------------------
/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "moduleResolution": "bundler",
9 | "allowImportingTsExtensions": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "noEmit": true,
13 | "jsx": "react-jsx",
14 | "strict": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "baseUrl": ".",
19 | "paths": {
20 | "@/*": ["./src/*"],
21 | "@typethings/functions": ["../packages/functions/src/index.ts"],
22 | "@typethings/editor": ["../packages/editor/src/index.ts"],
23 | "@typethings/ui": ["../packages/ui/src/index.tsx"]
24 | }
25 | },
26 | "include": [
27 | "src",
28 | "../packages/ui/src/index.tsx",
29 | "../packages/editor/src/index.ts"
30 | ],
31 | "references": [{ "path": "./tsconfig.node.json" }]
32 | }
33 |
--------------------------------------------------------------------------------
/app/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/app/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import path from "path";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig(async () => ({
7 | plugins: [react()],
8 | resolve: {
9 | alias: {
10 | "@": path.resolve(__dirname, "./src"),
11 | },
12 | },
13 | clearScreen: false,
14 | server: {
15 | port: 1420,
16 | strictPort: true,
17 | },
18 | envPrefix: ["VITE_", "TAURI_"],
19 | }));
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typethings/workspace",
3 | "author": "@pheralb_",
4 | "type": "module",
5 | "description": "A beautiful, minimal, and fast text editor built with Tauri.",
6 | "private": true,
7 | "packageManager": "pnpm@8.6.10",
8 | "scripts": {
9 | "typecheck-app": "cd app && pnpm typecheck",
10 | "typecheck-editor": "cd packages/editor && pnpm typecheck",
11 | "typecheck-ui": "cd packages/ui && pnpm typecheck",
12 | "build": "turbo run build",
13 | "dev": "turbo run dev",
14 | "dev:web": "cd website && pnpm dev",
15 | "lint": "turbo run lint",
16 | "format": "prettier --write \"**/*.{ts,tsx,md}\""
17 | },
18 | "devDependencies": {
19 | "@turbo/gen": "1.10.15",
20 | "@types/node": "20.8.3",
21 | "@types/react": "18.2.25",
22 | "@types/react-dom": "18.2.11",
23 | "@typethings/tailwind-config": "workspace:*",
24 | "eslint": "8.51.0",
25 | "prettier": "3.0.3",
26 | "prettier-plugin-tailwindcss": "0.5.5",
27 | "turbo": "latest"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/editor/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: ["plugin:react/recommended"],
7 | overrides: [
8 | {
9 | env: {
10 | node: true,
11 | },
12 | files: [".eslintrc.{js,cjs}"],
13 | parserOptions: {
14 | sourceType: "script",
15 | },
16 | },
17 | ],
18 | parserOptions: {
19 | ecmaVersion: "latest",
20 | sourceType: "module",
21 | project: "./tsconfig.json",
22 | },
23 | plugins: ["react"],
24 | rules: {},
25 | };
26 |
--------------------------------------------------------------------------------
/packages/editor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typethings/editor",
3 | "author": "@pheralb_",
4 | "description": "A WYSIWYG markdown editor built with React, Tailwind CSS & Tiptap",
5 | "version": "0.1.0",
6 | "license": "MIT",
7 | "sideEffects": [
8 | "**/*.css"
9 | ],
10 | "type": "module",
11 | "main": "./dist/index.js",
12 | "types": "./dist/index.d.ts",
13 | "typings": "./dist/index.d.ts",
14 | "files": [
15 | "dist/**"
16 | ],
17 | "exports": {
18 | ".": "./dist"
19 | },
20 | "scripts": {
21 | "build": "tsup",
22 | "lint": "eslint src/",
23 | "dev": "tsup --watch",
24 | "typecheck": "tsc --noEmit"
25 | },
26 | "dependencies": {
27 | "@radix-ui/react-tooltip": "1.0.7",
28 | "@tiptap/extension-code-block-lowlight": "2.1.12",
29 | "@tiptap/extension-color": "2.1.7",
30 | "@tiptap/extension-link": "2.1.11",
31 | "@tiptap/extension-table": "2.1.12",
32 | "@tiptap/extension-table-cell": "2.1.12",
33 | "@tiptap/extension-table-header": "2.1.12",
34 | "@tiptap/extension-table-row": "2.1.12",
35 | "@tiptap/extension-text-style": "2.1.7",
36 | "@tiptap/pm": "2.1.7",
37 | "@tiptap/react": "2.1.7",
38 | "@tiptap/starter-kit": "2.1.7",
39 | "clsx": "2.0.0",
40 | "highlight.js": "11.9.0",
41 | "lowlight": "3.1.0",
42 | "lucide-react": "0.271.0",
43 | "tailwind-merge": "1.14.0",
44 | "tiptap-markdown": "0.8.2"
45 | },
46 | "devDependencies": {
47 | "postcss": "^8.4.20",
48 | "react": "18.2.0",
49 | "tsup": "7.2.0",
50 | "typescript": "5.1.6"
51 | },
52 | "peerDependencies": {
53 | "react": "^18.2.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/editor/src/components/editor.tsx:
--------------------------------------------------------------------------------
1 | import type { CustomEditorProps } from "../types/editorProps";
2 |
3 | // Main Editor Component:
4 | import { EditorContent } from "@tiptap/react";
5 |
6 | const Editor = (props: CustomEditorProps) => {
7 | return (
8 | <>
9 | {props.children}
10 |
17 | >
18 | );
19 | };
20 |
21 | export { Editor };
22 |
--------------------------------------------------------------------------------
/packages/editor/src/components/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import type { iTooltipProps } from "../types/tooltipProps";
2 |
3 | import * as React from "react";
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5 | import clsx from "clsx";
6 |
7 | const TooltipProvider = TooltipPrimitive.Provider;
8 | const Tooltip = TooltipPrimitive.Root;
9 | const TooltipTrigger = TooltipPrimitive.Trigger;
10 |
11 | const TooltipContent = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, sideOffset = 4, ...props }, ref) => (
15 |
21 | ));
22 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
23 |
24 | export const BtnTooltip = (props: iTooltipProps) => {
25 | return (
26 |
27 |
28 | {props.children}
29 |
34 | {props.text}
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default BtnTooltip;
42 |
--------------------------------------------------------------------------------
/packages/editor/src/extensions/index.ts:
--------------------------------------------------------------------------------
1 | import type { Extensions as iTiptapExtensions } from "@tiptap/react";
2 |
3 | // Extensions:
4 | // ------------------
5 | import Color from "@tiptap/extension-color";
6 | import TextStyle from "@tiptap/extension-text-style";
7 | import StarterKit from "@tiptap/starter-kit";
8 | import Link from "@tiptap/extension-link";
9 | import Table from "@tiptap/extension-table";
10 | import TableCell from "@tiptap/extension-table-cell";
11 | import TableHeader from "@tiptap/extension-table-header";
12 | import TableRow from "@tiptap/extension-table-row";
13 | import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
14 | import { Markdown } from "tiptap-markdown";
15 | import { common, createLowlight } from "lowlight";
16 |
17 | // Custom code block:
18 | // ------------------
19 | import css from "highlight.js/lib/languages/css";
20 | import js from "highlight.js/lib/languages/javascript";
21 | import ts from "highlight.js/lib/languages/typescript";
22 | import html from "highlight.js/lib/languages/xml";
23 |
24 | const lowlight = createLowlight(common);
25 | lowlight.register("html", html);
26 | lowlight.register("css", css);
27 | lowlight.register("js", js);
28 | lowlight.register("ts", ts);
29 |
30 | // Table extensions:
31 | // -----------------
32 |
33 | const CustomTable = Table.extend({
34 | addAttributes() {
35 | return {
36 | ...this.parent?.(),
37 | style: {
38 | default: null,
39 | },
40 | };
41 | },
42 | });
43 |
44 | const CustomTableCell = TableCell.extend({
45 | addAttributes() {
46 | return {
47 | ...this.parent?.(),
48 | style: {
49 | default: null,
50 | },
51 | };
52 | },
53 | });
54 |
55 | // Create extensions config:
56 | // -------------------------
57 |
58 | export const Extensions: iTiptapExtensions = [
59 | StarterKit.configure({
60 | paragraph: {
61 | HTMLAttributes: {
62 | class: "m-0 leading-7",
63 | },
64 | },
65 | heading: {
66 | levels: [1, 2, 3, 4, 5, 6],
67 | HTMLAttributes: {
68 | class: "font-bold leading-7 [&:not(:first-child)]:mt-6",
69 | },
70 | },
71 | bulletList: {
72 | keepMarks: true,
73 | keepAttributes: false,
74 | HTMLAttributes: {
75 | class: "mt-0",
76 | },
77 | },
78 | orderedList: {
79 | keepMarks: true,
80 | keepAttributes: false,
81 | HTMLAttributes: {
82 | class: "-mt-2",
83 | },
84 | },
85 | }),
86 | Link.configure({
87 | openOnClick: false,
88 | validate: (href) => /^https?:\/\//.test(href),
89 | // HTMLAttributes: { rel: "noopener noreferrer" },
90 | }),
91 | CodeBlockLowlight.configure({
92 | lowlight,
93 | HTMLAttributes: {
94 | class:
95 | "w-full bg-neutral-200 dark:bg-neutral-900 border border-neutral-300 dark:border-neutral-800 text-dark",
96 | },
97 | }),
98 | CustomTable.configure({
99 | resizable: false,
100 | HTMLAttributes: {
101 | class: "w-full caption-bottom text-sm -mb-1",
102 | },
103 | }),
104 | TableRow.configure({
105 | HTMLAttributes: {
106 | class: "-my-2",
107 | },
108 | }),
109 | TableHeader.configure({
110 | HTMLAttributes: {
111 | class: "text-base",
112 | },
113 | }),
114 | CustomTableCell.configure({
115 | HTMLAttributes: {
116 | class: "-my-1",
117 | },
118 | }),
119 | Markdown.configure({
120 | html: true,
121 | tightLists: false,
122 | tightListClass: "tight",
123 | bulletListMarker: "-",
124 | transformPastedText: true,
125 | transformCopiedText: true,
126 | breaks: false,
127 | linkify: true,
128 | }),
129 | Color.configure({ types: [TextStyle.name] }),
130 |
131 | TextStyle,
132 | ];
133 |
--------------------------------------------------------------------------------
/packages/editor/src/index.ts:
--------------------------------------------------------------------------------
1 | export { useEditor } from "@tiptap/react";
2 | export type { Editor as iEditor } from "@tiptap/react";
3 |
4 | export { Editor } from "./components/editor";
5 | export { Menu } from "./components/menu";
6 | export { Extensions } from "./extensions";
--------------------------------------------------------------------------------
/packages/editor/src/types/editorProps.d.ts:
--------------------------------------------------------------------------------
1 | import type { EditorEvents } from "@tiptap/core";
2 | import type { Editor } from "@tiptap/react";
3 |
4 | export interface CustomEditorProps {
5 | children: React.ReactNode;
6 | spellCheck?: boolean;
7 | editor: Editor | null;
8 | defaultValue: string | undefined;
9 | onUpdate: (props: EditorEvents["update"]) => void;
10 | editorContentClassName?: string;
11 | autoFocus?: boolean;
12 | }
13 |
--------------------------------------------------------------------------------
/packages/editor/src/types/menuProps.d.ts:
--------------------------------------------------------------------------------
1 | import type { Editor } from "@tiptap/react";
2 |
3 | export interface MenuEditorProps {
4 | editor: Editor | null;
5 | iconSize?: number;
6 | btnClassName?: string;
7 | btnActiveClassName?: string;
8 | btnToolTipClassName?: string;
9 | btnGroupClassName?: string;
10 | btnGroupDividerClassName?: string;
11 | saveOnClickFn?: () => void;
12 | }
--------------------------------------------------------------------------------
/packages/editor/src/types/tooltipProps.d.ts:
--------------------------------------------------------------------------------
1 | export interface iTooltipProps {
2 | children: React.ReactNode;
3 | text: string;
4 | className?: string;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/editor/src/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
--------------------------------------------------------------------------------
/packages/editor/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "../shared/tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "dist",
6 | "baseUrl": ".",
7 | "paths": {
8 | "@/*": ["src/*"]
9 | }
10 | },
11 | "include": ["src/**/*.ts", "src/**/*.tsx"],
12 | "exclude": ["dist", "node_modules"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/editor/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, Options } from "tsup";
2 |
3 | export default defineConfig((options: Options) => ({
4 | treeshake: true,
5 | splitting: true,
6 | entry: ["src/index.ts"],
7 | format: ["esm"],
8 | dts: true,
9 | minify: true,
10 | clean: true,
11 | external: ["react"],
12 | tsconfig: "tsconfig.json",
13 | ...options,
14 | }));
15 |
--------------------------------------------------------------------------------
/packages/functions/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | overrides: [
7 | {
8 | env: {
9 | node: true,
10 | },
11 | files: [".eslintrc.{js,cjs}"],
12 | parserOptions: {
13 | sourceType: "script",
14 | },
15 | },
16 | ],
17 | parserOptions: {
18 | ecmaVersion: "latest",
19 | sourceType: "module",
20 | project: "./tsconfig.json",
21 | },
22 | plugins: [""],
23 | rules: {},
24 | };
25 |
--------------------------------------------------------------------------------
/packages/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typethings/functions",
3 | "author": "@pheralb_",
4 | "description": "A framework agnostic library to manage files and folders using Tauri API",
5 | "version": "0.1.0",
6 | "license": "MIT",
7 | "type": "module",
8 | "main": "./dist/index.js",
9 | "types": "./dist/index.d.ts",
10 | "typings": "./dist/index.d.ts",
11 | "files": [
12 | "dist/**"
13 | ],
14 | "exports": {
15 | ".": "./dist"
16 | },
17 | "scripts": {
18 | "build": "tsup",
19 | "lint": "eslint src/",
20 | "dev": "tsup --watch",
21 | "typecheck": "tsc --noEmit"
22 | },
23 | "dependencies": {
24 | "@tauri-apps/api": "1.5.0"
25 | },
26 | "devDependencies": {
27 | "tsup": "7.2.0",
28 | "typescript": "5.1.6"
29 | },
30 | "peerDependencies": {
31 | "@tauri-apps/api": "^1.5.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/functions/src/checkDirFile.ts:
--------------------------------------------------------------------------------
1 | import { exists, BaseDirectory } from "@tauri-apps/api/fs";
2 |
3 | export const checkDirFile = async (path: string) => {
4 | try {
5 | return await exists(path, { dir: BaseDirectory.AppData });
6 | } catch (error) {
7 | return console.log(error);
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/packages/functions/src/createFolder.ts:
--------------------------------------------------------------------------------
1 | import { BaseDirectory, createDir } from "@tauri-apps/api/fs";
2 | import { documentDir } from "@tauri-apps/api/path";
3 |
4 | export const createFolder = async (name: string) => {
5 | try {
6 | await createDir(name, { dir: BaseDirectory.Document, recursive: true });
7 | const getDocumentDir = await documentDir();
8 | const result = {
9 | success: true,
10 | directory: `${getDocumentDir}${name}`,
11 | };
12 | return result;
13 | } catch (error) {
14 | console.log(error);
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/packages/functions/src/createUpdateFile.ts:
--------------------------------------------------------------------------------
1 | import { writeTextFile } from "@tauri-apps/api/fs";
2 | import { join } from "@tauri-apps/api/path";
3 |
4 | interface iCreateNewFile {
5 | path: string;
6 | filename: string;
7 | extension: string;
8 | content: string;
9 | }
10 |
11 | interface iUpdateFile {
12 | path: string;
13 | content: string;
14 | }
15 |
16 | export const createFile = async ({
17 | path,
18 | filename,
19 | extension,
20 | content,
21 | }: iCreateNewFile) => {
22 | const fullPath = await join(path, `${filename}.${extension}`);
23 | await writeTextFile(fullPath, content);
24 | return fullPath;
25 | };
26 |
27 | export const updateFile = async ({ path, content }: iUpdateFile) => {
28 | await writeTextFile(path, content);
29 | return path;
30 | };
31 |
--------------------------------------------------------------------------------
/packages/functions/src/deleteFile.ts:
--------------------------------------------------------------------------------
1 | import { removeFile } from "@tauri-apps/api/fs";
2 |
3 | interface iDeleteFile {
4 | path: string;
5 | }
6 |
7 | export const deleteFile = async ({ path }: iDeleteFile) => {
8 | return await removeFile(path);
9 | };
10 |
--------------------------------------------------------------------------------
/packages/functions/src/getAllPlatformInfo.ts:
--------------------------------------------------------------------------------
1 | import type { OsType } from "@tauri-apps/api/os";
2 | import { platform, arch, type, version } from "@tauri-apps/api/os";
3 |
4 | export interface OSInfo {
5 | platform: string;
6 | architecture: string;
7 | osType: OsType;
8 | kernelVersion: string;
9 | }
10 |
11 | export const getOSInfo = async () => {
12 | try {
13 | const platformInfo = await platform();
14 | const architectureInfo = await arch();
15 | const osType = await type();
16 | const kernelVersion = await version();
17 | return {
18 | platform: platformInfo,
19 | architecture: architectureInfo,
20 | osType: osType,
21 | kernelVersion: kernelVersion,
22 | } as OSInfo;
23 | } catch (err) {
24 | console.error(err);
25 | return null;
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/packages/functions/src/getFileName.ts:
--------------------------------------------------------------------------------
1 | export const getFileName = (path: string) => {
2 | return path.split(/[\\/]/).pop();
3 | };
4 |
5 | export const getFileNameWithoutExtension = (path: string) => {
6 | return path.split(/[\\/]/).pop()!.split('.')[0];
7 | };
--------------------------------------------------------------------------------
/packages/functions/src/getFolderName.ts:
--------------------------------------------------------------------------------
1 | export const getFolderName = (path: string) => {
2 | return path.split(/[\\/]/).pop();
3 | };
4 |
--------------------------------------------------------------------------------
/packages/functions/src/index.ts:
--------------------------------------------------------------------------------
1 | // Check if directory exists:
2 | export * from "./checkDirFile";
3 |
4 | // Create, Update & Delete File:
5 | export * from "./createUpdateFile";
6 | export * from "./deleteFile";
7 |
8 | // Get OS, File & Folder info:
9 | export * from "./getAllPlatformInfo";
10 | export * from "./getFileName";
11 | export * from "./getFolderName";
12 |
13 | // Read File & Folders:
14 | export * from "./openFile";
15 | export * from "./readFiles";
16 |
17 | // Create & Select Folder:
18 | export * from "./createFolder";
19 | export * from "./selectFolder";
20 |
21 | // Export types:
22 | export type { FileEntry } from "@tauri-apps/api/fs";
23 |
--------------------------------------------------------------------------------
/packages/functions/src/openFile.ts:
--------------------------------------------------------------------------------
1 | import { open } from "@tauri-apps/api/dialog";
2 | import { dirname } from "@tauri-apps/api/path";
3 | import { getFolderName } from "./getFolderName";
4 |
5 | export const openFile = async () => {
6 | const selectedFile = await open({
7 | multiple: false,
8 | title: "Open File",
9 | filters: [
10 | {
11 | name: "Markdown Files",
12 | extensions: ["md"],
13 | },
14 | ],
15 | });
16 | if (!selectedFile) return;
17 | const folderPath = await dirname(selectedFile.toString());
18 | const folderName = getFolderName(folderPath);
19 | return {
20 | selectedFile,
21 | folderPath,
22 | folderName,
23 | } as {
24 | selectedFile: string;
25 | folderPath: string;
26 | folderName: string;
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/packages/functions/src/readFiles.ts:
--------------------------------------------------------------------------------
1 | import { FileEntry, readDir, readTextFile } from "@tauri-apps/api/fs";
2 |
3 | interface iReadFile {
4 | path: string;
5 | }
6 |
7 | export const readFilesFromFolder = async ({ path }: iReadFile) => {
8 | try {
9 | const result = await readDir(path);
10 | const onlyMdFiles = result.filter((file) => file.name!.endsWith("md"));
11 | return onlyMdFiles as FileEntry[];
12 | } catch (error) {}
13 | };
14 |
15 | export const readFile = async ({ path }: iReadFile) => {
16 | return await readTextFile(path);
17 | };
18 |
--------------------------------------------------------------------------------
/packages/functions/src/selectFolder.ts:
--------------------------------------------------------------------------------
1 | import { open } from "@tauri-apps/api/dialog";
2 | import { getFolderName } from "./getFolderName";
3 |
4 | export const selectFolder = async () => {
5 | const selectedFolder = await open({
6 | multiple: false,
7 | directory: true,
8 | title: "Open Folder",
9 | });
10 | if (!selectedFolder) return;
11 | const folderPath = selectedFolder.toString();
12 | const folderName = getFolderName(folderPath);
13 | return {
14 | folderPath,
15 | folderName,
16 | } as {
17 | folderPath: string;
18 | folderName: string;
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/packages/functions/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "../shared/tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "dist",
6 | "baseUrl": ".",
7 | "paths": {
8 | "@/*": ["src/*"]
9 | }
10 | },
11 | "include": ["src/**/*.ts", "src/**/*.tsx"],
12 | "exclude": ["dist", "node_modules"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/functions/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, Options } from "tsup";
2 |
3 | export default defineConfig((options: Options) => ({
4 | treeshake: true,
5 | splitting: true,
6 | entry: ["src/index.ts"],
7 | format: ["esm"],
8 | dts: true,
9 | minify: true,
10 | clean: true,
11 | external: ["react"],
12 | tsconfig: "tsconfig.json",
13 | ...options,
14 | }));
15 |
--------------------------------------------------------------------------------
/packages/shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "moduleResolution": "bundler",
9 | "allowImportingTsExtensions": true,
10 | "resolveJsonModule": true,
11 | "isolatedModules": true,
12 | "noEmit": true,
13 | "jsx": "react-jsx",
14 | "strict": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noFallthroughCasesInSwitch": true,
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/tailwind-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typethings/tailwind-config",
3 | "description": "Tailwind CSS Config for Typethings",
4 | "version": "0.1.0",
5 | "main": "index.ts",
6 | "types": "index.ts",
7 | "author": "@pheralb_",
8 | "homepage": "https://typethings.vercel.app",
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/pheralb/typethings.git"
12 | },
13 | "dependencies": {
14 | "@tailwindcss/nesting": "0.0.0-insiders.565cd3e",
15 | "@tailwindcss/typography": "0.5.9",
16 | "tailwindcss-animate": "1.0.7"
17 | },
18 | "devDependencies": {
19 | "tailwindcss": "3.3.3"
20 | },
21 | "keywords": [
22 | "dub",
23 | "dub.co",
24 | "tailwind",
25 | "tailwindcss"
26 | ],
27 | "publishConfig": {
28 | "access": "public"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/tailwind-config/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 | import { fontFamily } from "tailwindcss/defaultTheme";
3 |
4 | const config: Config = {
5 | darkMode: ["class"],
6 | content: [
7 | "./index.html",
8 | "./pages/**/*.{ts,tsx}",
9 | "./components/**/*.{ts,tsx}",
10 | "./app/**/*.{ts,tsx}",
11 | "./src/**/*.{ts,tsx}",
12 | ],
13 | theme: {
14 | container: {
15 | center: true,
16 | padding: "2rem",
17 | screens: {
18 | "2xl": "1400px",
19 | },
20 | },
21 | extend: {
22 | fontFamily: {
23 | sans: ["Inter-Roman", ...fontFamily.sans],
24 | mono: ["JetBrains-Mono", ...fontFamily.mono],
25 | },
26 | colors: {
27 | border: "hsl(var(--border))",
28 | input: "hsl(var(--input))",
29 | ring: "hsl(var(--ring))",
30 | background: "hsl(var(--background))",
31 | foreground: "hsl(var(--foreground))",
32 | primary: {
33 | DEFAULT: "hsl(var(--primary))",
34 | foreground: "hsl(var(--primary-foreground))",
35 | },
36 | secondary: {
37 | DEFAULT: "hsl(var(--secondary))",
38 | foreground: "hsl(var(--secondary-foreground))",
39 | },
40 | destructive: {
41 | DEFAULT: "hsl(var(--destructive))",
42 | foreground: "hsl(var(--destructive-foreground))",
43 | },
44 | muted: {
45 | DEFAULT: "hsl(var(--muted))",
46 | foreground: "hsl(var(--muted-foreground))",
47 | },
48 | accent: {
49 | DEFAULT: "hsl(var(--accent))",
50 | foreground: "hsl(var(--accent-foreground))",
51 | },
52 | popover: {
53 | DEFAULT: "hsl(var(--popover))",
54 | foreground: "hsl(var(--popover-foreground))",
55 | },
56 | card: {
57 | DEFAULT: "hsl(var(--card))",
58 | foreground: "hsl(var(--card-foreground))",
59 | },
60 | },
61 | typography: {
62 | quoteless: {
63 | css: {
64 | "blockquote p:first-of-type::before": { content: "none" },
65 | "blockquote p:first-of-type::after": { content: "none" },
66 | },
67 | },
68 | },
69 | borderRadius: {
70 | lg: "var(--radius)",
71 | md: "calc(var(--radius) - 2px)",
72 | sm: "calc(var(--radius) - 4px)",
73 | },
74 | keyframes: {
75 | "accordion-down": {
76 | from: { height: "0" },
77 | to: { height: "var(--radix-accordion-content-height)" },
78 | },
79 | "accordion-up": {
80 | from: { height: "var(--radix-accordion-content-height)" },
81 | to: { height: "0" },
82 | },
83 | },
84 | animation: {
85 | "accordion-down": "accordion-down 0.2s ease-out",
86 | "accordion-up": "accordion-up 0.2s ease-out",
87 | },
88 | },
89 | },
90 | plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
91 | };
92 | export default config;
93 |
--------------------------------------------------------------------------------
/packages/ui/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/packages/ui/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "src/styles/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/packages/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typethings/ui",
3 | "author": "@pheralb_",
4 | "description": "Typethings UI library built with React, Tailwind CSS and shadcn/ui",
5 | "version": "0.1.0",
6 | "license": "MIT",
7 | "sideEffects": false,
8 | "main": "./dist/index.js",
9 | "module": "./dist/index.mjs",
10 | "types": "./dist/index.d.ts",
11 | "files": [
12 | "dist/**"
13 | ],
14 | "scripts": {
15 | "build": "tsup",
16 | "dev": "tsup --watch",
17 | "dev-styles": "tailwindcss -i ./src/styles/globals.css -o ./dist/index.css --watch",
18 | "build-styles": "tailwindcss -i ./src/styles/globals.css -o ./dist/index.css --minify",
19 | "lint": "eslint src/",
20 | "typecheck": "tsc --noEmit"
21 | },
22 | "dependencies": {
23 | "@radix-ui/react-collapsible": "1.0.3",
24 | "@radix-ui/react-context-menu": "2.1.5",
25 | "@radix-ui/react-dialog": "1.0.5",
26 | "@radix-ui/react-dropdown-menu": "2.0.6",
27 | "@radix-ui/react-popover": "1.0.7",
28 | "@radix-ui/react-radio-group": "1.1.3",
29 | "@radix-ui/react-slot": "1.0.2",
30 | "@radix-ui/react-tabs": "1.0.4",
31 | "@radix-ui/react-tooltip": "1.0.7",
32 | "class-variance-authority": "0.7.0",
33 | "clsx": "2.0.0",
34 | "cmdk": "0.2.0",
35 | "lucide-react": "0.284.0",
36 | "react": "^18.2.0",
37 | "react-dom": "18.2.0",
38 | "tailwind-merge": "1.14.0"
39 | },
40 | "devDependencies": {
41 | "@tailwindcss/typography": "0.5.9",
42 | "@typescript-eslint/eslint-plugin": "6.7.4",
43 | "@typescript-eslint/parser": "6.7.4",
44 | "autoprefixer": "10.4.16",
45 | "eslint": "8.51.0",
46 | "eslint-plugin-react": "7.33.2",
47 | "postcss": "^8.4.31",
48 | "tailwindcss": "3.3.3",
49 | "tailwindcss-animate": "1.0.7",
50 | "tsup": "7.2.0",
51 | "typescript": "5.2.2"
52 | },
53 | "peerDependencies": {
54 | "react": "^18.2.0"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/packages/ui/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/packages/ui/src/components/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 | import { cn } from "..";
4 |
5 | const alertVariants = cva(
6 | "relative w-full rounded-sm px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
7 | {
8 | variants: {
9 | variant: {
10 | default: "bg-red-800/30 text-foreground",
11 | destructive:
12 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
13 | },
14 | },
15 | defaultVariants: {
16 | variant: "default",
17 | },
18 | },
19 | );
20 |
21 | const Alert = React.forwardRef<
22 | HTMLDivElement,
23 | React.HTMLAttributes & VariantProps
24 | >(({ className, variant, ...props }, ref) => (
25 |
31 | ));
32 | Alert.displayName = "Alert";
33 |
34 | const AlertTitle = React.forwardRef<
35 | HTMLParagraphElement,
36 | React.HTMLAttributes
37 | >(({ className, ...props }, ref) => (
38 |
43 | ));
44 | AlertTitle.displayName = "AlertTitle";
45 |
46 | const AlertDescription = React.forwardRef<
47 | HTMLParagraphElement,
48 | React.HTMLAttributes
49 | >(({ className, ...props }, ref) => (
50 |
55 | ));
56 | AlertDescription.displayName = "AlertDescription";
57 |
58 | export { Alert, AlertTitle, AlertDescription };
59 |
--------------------------------------------------------------------------------
/packages/ui/src/components/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import { cva, type VariantProps } from "class-variance-authority";
4 | import { cn } from "..";
5 |
6 | const buttonVariants = cva(
7 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 dark:text-white text-neutral-800 cursor-default",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "bg-neutral-700 text-white dark:bg-neutral-800 shadow hover:bg-neutral-700/60 dark:hover:bg-neutral-700/60",
13 | destructive:
14 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
15 | outline:
16 | "border border-neutral-300 dark:border-neutral-800 bg-transparent shadow-sm dark:hover:bg-neutral-800 hover:bg-neutral-400/20",
17 | secondary:
18 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
19 | ghost:
20 | "hover:bg-neutral-400/20 dark:hover:bg-neutral-700/40 dark:hover:text-white hover:text-neutral-900",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | },
35 | );
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean;
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button";
46 | return (
47 |
52 | );
53 | },
54 | );
55 | Button.displayName = "Button";
56 |
57 | export { Button, buttonVariants };
58 |
--------------------------------------------------------------------------------
/packages/ui/src/components/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8 |
9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
10 |
--------------------------------------------------------------------------------
/packages/ui/src/components/command.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { DialogProps } from "@radix-ui/react-dialog";
5 | import { Search } from "lucide-react";
6 | import { Command as CommandPrimitive } from "cmdk";
7 | import { cn } from "..";
8 | import { Dialog, DialogContent } from "./dialog";
9 |
10 | const Command = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
19 | ));
20 | Command.displayName = CommandPrimitive.displayName;
21 |
22 | interface CommandDialogProps extends DialogProps {}
23 |
24 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
25 | return (
26 |
27 |
28 |
29 | {children}
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | const CommandInput = React.forwardRef<
37 | React.ElementRef,
38 | React.ComponentPropsWithoutRef
39 | >(({ className, ...props }, ref) => (
40 |
44 |
45 |
53 |
54 | ));
55 |
56 | CommandInput.displayName = CommandPrimitive.Input.displayName;
57 |
58 | const CommandList = React.forwardRef<
59 | React.ElementRef,
60 | React.ComponentPropsWithoutRef
61 | >(({ className, ...props }, ref) => (
62 |
67 | ));
68 |
69 | CommandList.displayName = CommandPrimitive.List.displayName;
70 |
71 | const CommandEmpty = React.forwardRef<
72 | React.ElementRef,
73 | React.ComponentPropsWithoutRef
74 | >((props, ref) => (
75 |
80 | ));
81 |
82 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
83 |
84 | const CommandGroup = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ));
97 |
98 | CommandGroup.displayName = CommandPrimitive.Group.displayName;
99 |
100 | const CommandSeparator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
109 | ));
110 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
111 |
112 | const CommandItem = React.forwardRef<
113 | React.ElementRef,
114 | React.ComponentPropsWithoutRef
115 | >(({ className, ...props }, ref) => (
116 |
124 | ));
125 |
126 | CommandItem.displayName = CommandPrimitive.Item.displayName;
127 |
128 | const CommandShortcut = ({
129 | className,
130 | ...props
131 | }: React.HTMLAttributes) => {
132 | return (
133 |
140 | );
141 | };
142 | CommandShortcut.displayName = "CommandShortcut";
143 |
144 | export {
145 | Command,
146 | CommandDialog,
147 | CommandInput,
148 | CommandList,
149 | CommandEmpty,
150 | CommandGroup,
151 | CommandItem,
152 | CommandShortcut,
153 | CommandSeparator,
154 | };
155 |
--------------------------------------------------------------------------------
/packages/ui/src/components/context-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
5 | import { CheckIcon, ChevronRightIcon, DotIcon } from "lucide-react";
6 | import { cn } from "..";
7 |
8 | const ContextMenu = ContextMenuPrimitive.Root;
9 | const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
10 | const ContextMenuGroup = ContextMenuPrimitive.Group;
11 | const ContextMenuPortal = ContextMenuPrimitive.Portal;
12 | const ContextMenuSub = ContextMenuPrimitive.Sub;
13 | const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
14 |
15 | const ContextMenuSubTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef & {
18 | inset?: boolean;
19 | }
20 | >(({ className, inset, children, ...props }, ref) => (
21 |
30 | {children}
31 |
32 |
33 | ));
34 | ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
35 |
36 | const ContextMenuSubContent = React.forwardRef<
37 | React.ElementRef,
38 | React.ComponentPropsWithoutRef
39 | >(({ className, ...props }, ref) => (
40 |
48 | ));
49 | ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
50 |
51 | const ContextMenuContent = React.forwardRef<
52 | React.ElementRef,
53 | React.ComponentPropsWithoutRef
54 | >(({ className, ...props }, ref) => (
55 |
56 |
64 |
65 | ));
66 | ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
67 |
68 | const ContextMenuItem = React.forwardRef<
69 | React.ElementRef,
70 | React.ComponentPropsWithoutRef & {
71 | inset?: boolean;
72 | }
73 | >(({ className, inset, ...props }, ref) => (
74 |
83 | ));
84 | ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
85 |
86 | const ContextMenuCheckboxItem = React.forwardRef<
87 | React.ElementRef,
88 | React.ComponentPropsWithoutRef
89 | >(({ className, children, checked, ...props }, ref) => (
90 |
99 |
100 |
101 |
102 |
103 |
104 | {children}
105 |
106 | ));
107 | ContextMenuCheckboxItem.displayName =
108 | ContextMenuPrimitive.CheckboxItem.displayName;
109 |
110 | const ContextMenuRadioItem = React.forwardRef<
111 | React.ElementRef,
112 | React.ComponentPropsWithoutRef
113 | >(({ className, children, ...props }, ref) => (
114 |
122 |
123 |
124 |
125 |
126 |
127 | {children}
128 |
129 | ));
130 | ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
131 |
132 | const ContextMenuLabel = React.forwardRef<
133 | React.ElementRef,
134 | React.ComponentPropsWithoutRef & {
135 | inset?: boolean;
136 | }
137 | >(({ className, inset, ...props }, ref) => (
138 |
147 | ));
148 | ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
149 |
150 | const ContextMenuSeparator = React.forwardRef<
151 | React.ElementRef,
152 | React.ComponentPropsWithoutRef
153 | >(({ className, ...props }, ref) => (
154 |
159 | ));
160 | ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
161 |
162 | const ContextMenuShortcut = ({
163 | className,
164 | ...props
165 | }: React.HTMLAttributes) => {
166 | return (
167 |
174 | );
175 | };
176 | ContextMenuShortcut.displayName = "ContextMenuShortcut";
177 |
178 | export {
179 | ContextMenu,
180 | ContextMenuTrigger,
181 | ContextMenuContent,
182 | ContextMenuItem,
183 | ContextMenuCheckboxItem,
184 | ContextMenuRadioItem,
185 | ContextMenuLabel,
186 | ContextMenuSeparator,
187 | ContextMenuShortcut,
188 | ContextMenuGroup,
189 | ContextMenuPortal,
190 | ContextMenuSub,
191 | ContextMenuSubContent,
192 | ContextMenuSubTrigger,
193 | ContextMenuRadioGroup,
194 | };
195 |
--------------------------------------------------------------------------------
/packages/ui/src/components/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as DialogPrimitive from "@radix-ui/react-dialog";
5 | import { X } from "lucide-react";
6 | import { cn } from "..";
7 |
8 | const Dialog = DialogPrimitive.Root;
9 |
10 | const DialogClose = DialogPrimitive.Close;
11 |
12 | const DialogTrigger = DialogPrimitive.Trigger;
13 |
14 | const DialogPortal = DialogPrimitive.Portal;
15 |
16 | const DialogOverlay = React.forwardRef<
17 | React.ElementRef,
18 | React.ComponentPropsWithoutRef
19 | >(({ className, ...props }, ref) => (
20 |
28 | ));
29 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
30 |
31 | const DialogContent = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef
34 | >(({ className, children, ...props }, ref) => (
35 |
36 |
37 |
45 | {children}
46 |
47 |
48 | Close
49 |
50 |
51 |
52 | ));
53 | DialogContent.displayName = DialogPrimitive.Content.displayName;
54 |
55 | const DialogHeader = ({
56 | className,
57 | ...props
58 | }: React.HTMLAttributes) => (
59 |
66 | );
67 | DialogHeader.displayName = "DialogHeader";
68 |
69 | const DialogFooter = ({
70 | className,
71 | ...props
72 | }: React.HTMLAttributes) => (
73 |
80 | );
81 | DialogFooter.displayName = "DialogFooter";
82 |
83 | const DialogTitle = React.forwardRef<
84 | React.ElementRef,
85 | React.ComponentPropsWithoutRef
86 | >(({ className, ...props }, ref) => (
87 |
92 | ));
93 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
94 |
95 | const DialogDescription = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, ...props }, ref) => (
99 |
104 | ));
105 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
106 |
107 | export {
108 | Dialog,
109 | DialogClose,
110 | DialogPortal,
111 | DialogOverlay,
112 | DialogTrigger,
113 | DialogContent,
114 | DialogHeader,
115 | DialogFooter,
116 | DialogTitle,
117 | DialogDescription,
118 | };
119 |
--------------------------------------------------------------------------------
/packages/ui/src/components/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5 | import { ArrowDown } from "lucide-react";
6 | import { cn } from "../utils/cn";
7 |
8 | const DropdownMenu = DropdownMenuPrimitive.Root;
9 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
10 |
11 | const DropdownMenuSubTrigger = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef & {
14 | inset?: boolean;
15 | }
16 | >(({ className, inset, children, ...props }, ref) => (
17 |
26 | {children}
27 |
28 |
29 | ));
30 | DropdownMenuSubTrigger.displayName =
31 | DropdownMenuPrimitive.SubTrigger.displayName;
32 |
33 | const DropdownMenuSubContent = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ));
46 | DropdownMenuSubContent.displayName =
47 | DropdownMenuPrimitive.SubContent.displayName;
48 |
49 | const DropdownMenuContent = React.forwardRef<
50 | React.ElementRef,
51 | React.ComponentPropsWithoutRef
52 | >(({ className, sideOffset = 4, ...props }, ref) => (
53 |
54 |
64 |
65 | ));
66 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
67 |
68 | const DropdownMenuItem = React.forwardRef<
69 | React.ElementRef,
70 | React.ComponentPropsWithoutRef & {
71 | inset?: boolean;
72 | }
73 | >(({ className, inset, ...props }, ref) => (
74 |
83 | ));
84 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
85 |
86 | const DropdownMenuSeparator = React.forwardRef<
87 | React.ElementRef,
88 | React.ComponentPropsWithoutRef
89 | >(({ className, ...props }, ref) => (
90 |
95 | ));
96 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
97 |
98 | const DropdownMenuShortcut = ({
99 | className,
100 | ...props
101 | }: React.HTMLAttributes) => {
102 | return (
103 |
107 | );
108 | };
109 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
110 |
111 | export {
112 | DropdownMenu,
113 | DropdownMenuTrigger,
114 | DropdownMenuContent,
115 | DropdownMenuItem,
116 | DropdownMenuShortcut,
117 | };
118 |
--------------------------------------------------------------------------------
/packages/ui/src/components/externalLink.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 | import { cn } from "..";
5 | import { ArrowUpRight } from "lucide-react";
6 |
7 | interface LinkProps extends React.AnchorHTMLAttributes {
8 | children: ReactNode;
9 | className?: string;
10 | underline?: boolean;
11 | showIcon?: boolean;
12 | }
13 |
14 | export const ExternalLink = (props: LinkProps) => {
15 | return (
16 |
26 | {props.children}
27 | {props.showIcon && }
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/packages/ui/src/components/formGroup.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 | import { cn } from "..";
5 |
6 | interface iFormGroupProps {
7 | children: ReactNode;
8 | className?: string;
9 | }
10 |
11 | const FormGroup = (props: iFormGroupProps) => {
12 | return (
13 |
14 | {props.children}
15 |
16 | );
17 | };
18 |
19 | export { FormGroup };
20 |
--------------------------------------------------------------------------------
/packages/ui/src/components/input.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { cn } from "..";
5 |
6 | export interface InputProps
7 | extends React.InputHTMLAttributes {}
8 |
9 | const Input = React.forwardRef(
10 | ({ className, type, ...props }, ref) => {
11 | return (
12 |
22 | );
23 | },
24 | );
25 | Input.displayName = "Input";
26 |
27 | export { Input };
28 |
--------------------------------------------------------------------------------
/packages/ui/src/components/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as PopoverPrimitive from "@radix-ui/react-popover";
5 | import { cn } from "..";
6 |
7 | const Popover = PopoverPrimitive.Root;
8 |
9 | const PopoverTrigger = PopoverPrimitive.Trigger;
10 |
11 | const PopoverContent = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
15 |
16 |
26 |
27 | ));
28 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
29 |
30 | export { Popover, PopoverTrigger, PopoverContent };
31 |
--------------------------------------------------------------------------------
/packages/ui/src/components/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { CheckIcon } from "lucide-react";
5 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
6 | import { cn } from "..";
7 |
8 | const RadioGroup = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => {
12 | return (
13 |
18 | );
19 | });
20 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
21 |
22 | const RadioGroupItem = React.forwardRef<
23 | React.ElementRef,
24 | React.ComponentPropsWithoutRef
25 | >(({ className, children, ...props }, ref) => {
26 | return (
27 |
35 |
36 |
37 |
38 |
39 | );
40 | });
41 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
42 |
43 | export { RadioGroup, RadioGroupItem };
44 |
--------------------------------------------------------------------------------
/packages/ui/src/components/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TabsPrimitive from "@radix-ui/react-tabs";
5 | import { cn } from "..";
6 |
7 | const Tabs = TabsPrimitive.Root;
8 |
9 | const TabsList = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 | ));
22 | TabsList.displayName = TabsPrimitive.List.displayName;
23 |
24 | const TabsTrigger = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, ...props }, ref) => (
28 |
36 | ));
37 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
38 |
39 | const TabsContent = React.forwardRef<
40 | React.ElementRef,
41 | React.ComponentPropsWithoutRef
42 | >(({ className, ...props }, ref) => (
43 |
51 | ));
52 | TabsContent.displayName = TabsPrimitive.Content.displayName;
53 |
54 | export { Tabs, TabsList, TabsTrigger, TabsContent };
55 |
--------------------------------------------------------------------------------
/packages/ui/src/components/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5 | import { cn } from "..";
6 |
7 | const TooltipProvider = TooltipPrimitive.Provider;
8 |
9 | const Tooltip = TooltipPrimitive.Root;
10 |
11 | const TooltipTrigger = TooltipPrimitive.Trigger;
12 |
13 | const TooltipStyles = cn(
14 | "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md bg-neutral-200 dark:bg-neutral-800 px-3 py-1.5 text-sm dark:text-neutral-200",
15 | );
16 |
17 | const TooltipContent = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, sideOffset = 4, ...props }, ref) => (
21 |
27 | ));
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
29 |
30 | export {
31 | Tooltip,
32 | TooltipStyles,
33 | TooltipTrigger,
34 | TooltipContent,
35 | TooltipProvider,
36 | };
37 |
--------------------------------------------------------------------------------
/packages/ui/src/extend/prose.ts:
--------------------------------------------------------------------------------
1 | import { cn } from "..";
2 |
3 | export const ProseClasses = cn(
4 | // Global Prose:
5 | "prose prose-quoteless prose-neutral dark:prose-invert prose-p:my-4 prose-a:underline-offset-4 prose-a:decoration-neutral-400 prose-a:decoration-solid prose-a:cursor-pointer prose-a:cursor-ne-resize"
6 | );
7 |
--------------------------------------------------------------------------------
/packages/ui/src/index.tsx:
--------------------------------------------------------------------------------
1 | // Styles:
2 | import "./styles/globals.css";
3 |
4 | // `cn` util with clsx & Tailwind Merge:
5 | export { cn } from "./utils/cn";
6 |
7 | // Components:
8 | export * from "./components/alert";
9 | export * from "./components/button";
10 | export * from "./components/collapsible";
11 | export * from "./components/command";
12 | export * from "./components/context-menu";
13 | export * from "./components/dialog";
14 | export * from "./components/dropdown-menu";
15 | export * from "./components/externalLink";
16 | export * from "./components/formGroup";
17 | export * from "./components/input";
18 | export * from "./components/popover";
19 | export * from "./components/radio-group";
20 | export * from "./components/tooltip";
21 | export * from "./components/tabs";
22 |
23 | // Extend Config:
24 | export { ProseClasses } from "./extend/prose";
25 |
--------------------------------------------------------------------------------
/packages/ui/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/packages/ui/src/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/packages/ui/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import sharedConfig from "@typethings/tailwind-config/tailwind.config.ts";
2 | import type { Config } from "tailwindcss";
3 |
4 | const config: Pick = {
5 | presets: [sharedConfig],
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "../shared/tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "dist",
6 | "baseUrl": ".",
7 | "paths": {
8 | "@/*": ["src/*"]
9 | }
10 | },
11 | "include": ["."],
12 | "exclude": ["dist", "node_modules"]
13 | }
14 |
--------------------------------------------------------------------------------
/packages/ui/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, Options } from "tsup";
2 |
3 | export default defineConfig((options: Options) => ({
4 | entry: ["src/**/*.tsx", "src/**/*.ts"],
5 | format: ["esm"],
6 | esbuildOptions(options) {
7 | options.banner = {
8 | js: '"use client"',
9 | };
10 | },
11 | dts: true,
12 | minify: true,
13 | clean: true,
14 | external: ["react"],
15 | ...options,
16 | }));
17 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "app"
3 | - "website"
4 | - "packages/*"
5 |
--------------------------------------------------------------------------------
/prettier.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import("prettier").Config} */
2 | module.exports = {
3 | plugins: ['prettier-plugin-tailwindcss'],
4 | };
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": ["**/.env.*local"],
4 | "pipeline": {
5 | "build": {
6 | "dependsOn": ["^build"],
7 | "outputs": [".next/**", "!.next/cache/**"]
8 | },
9 | "lint": {},
10 | "dev": {
11 | "cache": false,
12 | "persistent": true
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/website/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["next-on-pages"],
3 | "extends": ["next/core-web-vitals", "plugin:next-on-pages/recommended"],
4 | "rules": {
5 | "next-on-pages/no-unsupported-configs": "warn"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/website/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | swcMinify: true,
4 | reactStrictMode: true,
5 | transpilePackages: ["@typethings/ui"],
6 | };
7 |
8 | export default nextConfig;
9 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@typethings/website",
3 | "author": "@pheralb_",
4 | "description": "Typethings website",
5 | "version": "0.1.0",
6 | "private": true,
7 | "scripts": {
8 | "dev": "next dev",
9 | "build": "next build",
10 | "start": "next start",
11 | "lint": "next lint",
12 | "pages:build": "npx @cloudflare/next-on-pages",
13 | "pages:deploy": "npm run pages:build && wrangler pages deploy .vercel/output/static",
14 | "pages:watch": "npx @cloudflare/next-on-pages --watch",
15 | "pages:dev": "npx wrangler pages dev .vercel/output/static --compatibility-date=2023-10-30 --compatibility-flag=nodejs_compat"
16 | },
17 | "dependencies": {
18 | "@typethings/tailwind-config": "workspace:*",
19 | "@typethings/ui": "workspace:*",
20 | "lucide-react": "0.292.0",
21 | "next": "14.0.1",
22 | "react": "18.2.0",
23 | "react-dom": "18.2.0"
24 | },
25 | "devDependencies": {
26 | "@cloudflare/next-on-pages": "1.7.2",
27 | "@types/node": "20.8.10",
28 | "@types/react": "18.2.35",
29 | "@types/react-dom": "18.2.14",
30 | "autoprefixer": "10.4.16",
31 | "eslint": "8.53.0",
32 | "eslint-config-next": "14.0.1",
33 | "eslint-plugin-next-on-pages": "1.7.2",
34 | "postcss": "8.4.31",
35 | "tailwindcss": "3.3.3",
36 | "typescript": "5.2.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/website/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/website/public/fonts/Inter-roman.var.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/website/public/fonts/Inter-roman.var.woff2
--------------------------------------------------------------------------------
/website/public/fonts/JetBrainsMono-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/website/public/fonts/JetBrainsMono-Regular.woff2
--------------------------------------------------------------------------------
/website/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/website/public/images/logo.png
--------------------------------------------------------------------------------
/website/public/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/public/images/screenshot_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/website/public/images/screenshot_en.png
--------------------------------------------------------------------------------
/website/public/images/screenshot_es.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheralb/typethings/6a6fc777cc63c7cb4eea06dc97a61f1eac85ca14/website/public/images/screenshot_es.png
--------------------------------------------------------------------------------
/website/src/app/api/hello/route.ts:
--------------------------------------------------------------------------------
1 | import type { NextRequest } from "next/server";
2 |
3 | export const runtime = "edge";
4 |
5 | export async function GET(request: NextRequest) {
6 | return new Response(JSON.stringify({ name: "John Doe" }));
7 | }
8 |
--------------------------------------------------------------------------------
/website/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 |
3 | // Global styles:
4 | import "@/styles/globals.css";
5 | import "@typethings/ui/dist/index.css";
6 | import { cn } from "@typethings/ui";
7 |
8 | // Layout:
9 | import Navbar from "@/components/navbar";
10 |
11 | // Metadata:
12 | export const metadata: Metadata = {
13 | title: {
14 | default: "Typethings - A beautiful note taking app.",
15 | template: "%s | Typethings",
16 | },
17 | description: "A simple, beautiful and powerful markdown editor",
18 | icons: ["/images/logo.svg"],
19 | openGraph: {
20 | title: "Typethings",
21 | description: "A beautiful note taking app.",
22 | url: "https://typethings.app",
23 | siteName: "Typethings",
24 | locale: "en_US",
25 | // images: [
26 | // {
27 | // url: "https://typethings.app/images/og-image.png",
28 | // width: 1200,
29 | // height: 630,
30 | // alt: "Typethings",
31 | // },
32 | // ],
33 | type: "website",
34 | },
35 | robots: {
36 | index: true,
37 | follow: true,
38 | googleBot: {
39 | index: true,
40 | follow: true,
41 | "max-video-preview": -1,
42 | "max-image-preview": "large",
43 | "max-snippet": -1,
44 | },
45 | },
46 | twitter: {
47 | title: "Typethings",
48 | card: "summary_large_image",
49 | },
50 | };
51 |
52 | // Main app:
53 | export default function RootLayout({
54 | children,
55 | }: {
56 | children: React.ReactNode;
57 | }) {
58 | return (
59 |
60 |
65 |
66 | {children}
67 |
68 |
69 | );
70 | }
71 |
--------------------------------------------------------------------------------
/website/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import CardSpotlight from "@/components/cardSpotlight";
2 | import Container from "@/components/container";
3 | import Mac from "@/components/icons/mac";
4 | import Windows from "@/components/icons/windows";
5 | import { Global } from "@/global/data";
6 | import { ExternalLink, buttonVariants, cn } from "@typethings/ui";
7 | import { Book, Download } from "lucide-react";
8 | import Image from "next/image";
9 | import Link from "next/link";
10 | import { userAgent } from "next/server";
11 |
12 | export default function Home() {
13 | const getOsInfo = () => {
14 | const isMac =
15 | typeof window !== "undefined"
16 | ? navigator.platform.toUpperCase().indexOf("MAC") >= 0
17 | : false;
18 | return isMac ? "for Mac" : "for Windows";
19 | };
20 |
21 | return (
22 | <>
23 |
24 |
25 |
26 |
27 |
28 | A beautiful note taking app.
29 |
30 |
31 | Typethings is a simple, beautiful and powerful markdown editor
32 | with a minimalistic design. It{"'"}s 💸 free, 🔒 private and ✨
33 | open source.
34 |
35 |
36 |
43 |
44 |
Get started
45 |
46 |
56 |
57 |
58 | {getOsInfo() === "for Windows" ? (
59 |
60 | ) : (
61 |
62 | )}
63 | Download {getOsInfo()}
64 |
65 |
66 |
67 |
68 | {/* eslint-disable-next-line @next/next/no-img-element */}
69 |
74 |
75 |
76 |
77 |
78 | {/*
79 |
80 | */}
81 |
82 | >
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/website/src/components/cardSpotlight.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useRef, useState } from "react";
3 |
4 | const CardSpotlight = () => {
5 | const divRef = useRef(null);
6 | const [isFocused, setIsFocused] = useState(false);
7 | const [position, setPosition] = useState({ x: 0, y: 0 });
8 | const [opacity, setOpacity] = useState(0);
9 |
10 | const handleMouseMove = (e: React.MouseEvent) => {
11 | if (!divRef.current || isFocused) return;
12 | const div = divRef.current;
13 | const rect = div.getBoundingClientRect();
14 | setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
15 | };
16 |
17 | const handleFocus = () => {
18 | setIsFocused(true);
19 | setOpacity(1);
20 | };
21 |
22 | const handleBlur = () => {
23 | setIsFocused(false);
24 | setOpacity(0);
25 | };
26 |
27 | const handleMouseEnter = () => {
28 | setOpacity(1);
29 | };
30 |
31 | const handleMouseLeave = () => {
32 | setOpacity(0);
33 | };
34 |
35 | return (
36 |
45 |
52 |
Card Content
53 |
54 | );
55 | };
56 |
57 | export default CardSpotlight;
58 |
--------------------------------------------------------------------------------
/website/src/components/container.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@typethings/ui";
2 | import type { ReactNode } from "react";
3 |
4 | interface ContainerProps {
5 | className?: string;
6 | children: ReactNode;
7 | }
8 |
9 | const Container = (props: ContainerProps) => {
10 | return (
11 | {props.children}
12 | );
13 | };
14 |
15 | export default Container;
16 |
--------------------------------------------------------------------------------
/website/src/components/hero.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Hero = () => {
4 | return (
5 | Hero
6 | )
7 | }
8 |
9 | export default Hero
--------------------------------------------------------------------------------
/website/src/components/icons/mac.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentProps, FC } from "react";
2 |
3 | const Mac: FC> = (props) => (
4 |
5 |
6 |
7 | );
8 |
9 | export default Mac;
10 |
--------------------------------------------------------------------------------
/website/src/components/icons/typethings.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentProps, FC } from "react";
2 |
3 | const TypethingsIcon: FC> = (props) => (
4 |
11 |
24 |
25 |
26 |
27 |
28 |
37 |
38 |
39 |
40 |
47 |
48 |
49 |
50 |
51 |
62 |
63 |
64 |
65 |
66 | );
67 |
68 | export default TypethingsIcon;
69 |
--------------------------------------------------------------------------------
/website/src/components/icons/windows.tsx:
--------------------------------------------------------------------------------
1 | import type { ComponentProps, FC } from "react";
2 |
3 | const Windows: FC> = (props) => (
4 |
9 |
13 |
14 | );
15 |
16 | export default Windows;
17 |
--------------------------------------------------------------------------------
/website/src/components/navbar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Socials from "./socials";
3 | import TypethingsIcon from "./icons/typethings";
4 | import Link from "next/link";
5 | import { buttonVariants } from "@typethings/ui";
6 | import Container from "./container";
7 | import { Global } from "@/global/data";
8 |
9 | const Navbar = () => {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Typethings
19 |
20 |
21 |
25 | Docs
26 |
27 |
28 |
29 |
30 |
41 | Download
42 |
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | export default Navbar;
51 |
--------------------------------------------------------------------------------
/website/src/components/socials.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React from "react";
4 | import { Twitter, Github } from "lucide-react";
5 | import { ExternalLink } from "@typethings/ui";
6 |
7 | const Socials = () => {
8 | const iconSize = 22;
9 | const links = [
10 | {
11 | name: "Twitter",
12 | url: "https://twitter.com/pheralb_",
13 | icon: Twitter,
14 | },
15 | {
16 | name: "Github",
17 | url: "https://github.com/pheralb/typethings",
18 | icon: Github,
19 | },
20 | ];
21 | return (
22 |
23 | {links.map((link) => (
24 |
30 |
31 |
32 | ))}
33 |
34 | );
35 | };
36 |
37 | export default Socials;
38 |
--------------------------------------------------------------------------------
/website/src/global/data.ts:
--------------------------------------------------------------------------------
1 | export interface iGlobalData {
2 | activateDownload: boolean;
3 | downloadUrl: string;
4 | }
5 |
6 | export const Global: iGlobalData = {
7 | activateDownload: false,
8 | downloadUrl: "",
9 | };
--------------------------------------------------------------------------------
/website/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 0 0% 3.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 0 0% 3.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 0 0% 3.9%;
15 |
16 | --primary: 0 0% 9%;
17 | --primary-foreground: 0 0% 98%;
18 |
19 | --secondary: 0 0% 96.1%;
20 | --secondary-foreground: 0 0% 9%;
21 |
22 | --muted: 0 0% 96.1%;
23 | --muted-foreground: 0 0% 45.1%;
24 |
25 | --accent: 0 0% 96.1%;
26 | --accent-foreground: 0 0% 9%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 0 0% 98%;
30 |
31 | --border: 0 0% 89.8%;
32 | --input: 0 0% 89.8%;
33 | --ring: 0 0% 3.9%;
34 |
35 | --radius: 0.5rem;
36 |
37 | --sb-track-color: rgb(229 229 229 / 0.5);
38 | --sb-thumb-color: #d4d4d4;
39 | --sb-size: 7px;
40 |
41 | --code-bg: #e5e5e5;
42 | }
43 |
44 | .dark {
45 | --background: 0 0% 3.9%;
46 | --foreground: 0 0% 98%;
47 |
48 | --card: 0 0% 3.9%;
49 | --card-foreground: 0 0% 98%;
50 |
51 | --popover: 0 0% 3.9%;
52 | --popover-foreground: 0 0% 98%;
53 |
54 | --primary: 0 0% 98%;
55 | --primary-foreground: 0 0% 9%;
56 |
57 | --secondary: 0 0% 14.9%;
58 | --secondary-foreground: 0 0% 98%;
59 |
60 | --muted: 0 0% 14.9%;
61 | --muted-foreground: 0 0% 63.9%;
62 |
63 | --accent: 0 0% 14.9%;
64 | --accent-foreground: 0 0% 98%;
65 |
66 | --destructive: 0 62.8% 30.6%;
67 | --destructive-foreground: 0 0% 98%;
68 |
69 | --border: 0 0% 14.9%;
70 | --input: 0 0% 14.9%;
71 | --ring: 0 0% 83.1%;
72 |
73 | --sb-track-color: #171717;
74 | --sb-thumb-color: #404040;
75 | --sb-size: 7px;
76 |
77 | --code-bg: #171717;
78 | }
79 | }
80 |
81 | @layer base {
82 | * {
83 | @apply border-border;
84 | }
85 | }
86 |
87 | @font-face {
88 | font-family: "Inter-Roman";
89 | src: url("/fonts/Inter-roman.var.woff2") format("woff2");
90 | font-weight: 100 900;
91 | font-display: swap;
92 | font-style: normal;
93 | }
94 |
--------------------------------------------------------------------------------
/website/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import sharedConfig from "@typethings/tailwind-config/tailwind.config";
2 | import type { Config } from "tailwindcss";
3 |
4 | const config: Pick = {
5 | presets: [
6 | {
7 | ...sharedConfig,
8 | content: [
9 | "./index.html",
10 | "./src/**/*.{ts,tsx}",
11 | "../packages/ui/src/**/*{.js,.ts,.jsx,.tsx}",
12 | ],
13 | },
14 | ],
15 | };
16 |
17 | export default config;
18 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"],
23 | "@typethings/editor": ["../packages/editor/src/index.ts"],
24 | "@typethings/ui": ["../packages/ui/src/index.tsx"]
25 | }
26 | },
27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28 | "exclude": ["node_modules"]
29 | }
30 |
--------------------------------------------------------------------------------