├── .babelrc
├── .github
└── workflows
│ ├── production-build.yaml
│ └── publish-to-market.yml
├── .gitignore
├── .vscode
├── launch.json
└── settings.json
├── .vscodeignore
├── .yarnrc
├── LICENSE
├── README.md
├── contributing.md
├── docs
└── assets
│ ├── Paper File bookmark.gif
│ ├── Paper Filetree.gif
│ ├── Paper Selection bookmark.gif
│ └── PaperBanner.png
├── icons
├── icon.png
└── tiny.png
├── package.json
├── prettier.config.js
├── src
├── extension
│ ├── ContentProvider.ts
│ ├── api
│ │ ├── DocsManager.ts
│ │ ├── index.ts
│ │ ├── static-files.ts
│ │ └── utils.ts
│ ├── extension.ts
│ └── typings.d.ts
└── ui
│ ├── App.tsx
│ ├── Editor.tsx
│ ├── EditorFloatingButton.tsx
│ ├── EventsListener.ts
│ ├── assets
│ ├── JsBubblesLogo.png
│ └── logo.png
│ ├── docs-list
│ ├── DocItem.tsx
│ └── index.tsx
│ ├── editor-views
│ ├── file-bookmark
│ │ ├── Component.tsx
│ │ ├── index.tsx
│ │ └── styles.css
│ ├── file-tree
│ │ ├── Component.tsx
│ │ ├── Node.tsx
│ │ ├── index.tsx
│ │ └── styles.css
│ └── selection-bookmark
│ │ ├── Component.tsx
│ │ ├── index.tsx
│ │ └── styles.css
│ ├── index.tsx
│ ├── styles.css
│ ├── theme.ts
│ ├── types.ts
│ └── typings.d.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | [
5 | "@babel/preset-typescript",
6 | {
7 | "isTSX": true,
8 | "allExtensions": true
9 | }
10 | ],
11 | "@babel/preset-env"
12 | ],
13 | "plugins": [
14 | ["@babel/plugin-proposal-decorators", { "legacy": true }],
15 | ["@babel/plugin-proposal-class-properties", { "loose": true }],
16 | "@babel/plugin-proposal-object-rest-spread",
17 | "babel-plugin-styled-components",
18 | "@babel/plugin-transform-runtime",
19 | "@babel/plugin-syntax-optional-chaining",
20 | "@babel/plugin-proposal-optional-chaining"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/production-build.yaml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 |
9 | strategy:
10 | matrix:
11 | os: [ubuntu-latest, windows-latest]
12 | node-version: [10.x]
13 |
14 | steps:
15 | - uses: actions/checkout@v1
16 | - name: Use Node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - name: yarn install and build
21 | run: |
22 | yarn install --ignore-engines
23 | yarn build
24 | env:
25 | CI: true
26 |
--------------------------------------------------------------------------------
/.github/workflows/publish-to-market.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | tags:
4 | - '*'
5 | name: Publish to market
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Use Node.js
13 | uses: actions/setup-node@v1
14 | with:
15 | node-version: 10.x
16 | - name: yarn install, build, and test
17 | run: |
18 | yarn install --ignore-engines
19 | yarn build
20 | - uses: lannonbr/vsce-action@master
21 | with:
22 | args: 'publish -p $VSCE_TOKEN --yarn'
23 | env:
24 | CI: true
25 | VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }}
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | out
3 | .vscode-test
4 | cypress/videos
5 | cypress/screenshots
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "runtimeExecutable": "${execPath}",
13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"],
14 | "outFiles": ["${workspaceFolder}/out/**/*.js"],
15 | "env": {
16 | "dev": "true"
17 | }
18 | },
19 | {
20 | "name": "Extension Tests",
21 | "type": "extensionHost",
22 | "request": "launch",
23 | "runtimeExecutable": "${execPath}",
24 | "args": [
25 | "${workspaceFolder}/src/test/dummy-project-used-by-tests",
26 | "--extensionDevelopmentPath=${workspaceFolder}",
27 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
28 | ],
29 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"]
30 | },
31 | {
32 | "type": "node",
33 | "request": "launch",
34 | "name": "Jest Current File",
35 | "program": "${workspaceFolder}/node_modules/.bin/jest",
36 | "args": ["${fileBasename}"],
37 | "console": "integratedTerminal",
38 | "internalConsoleOptions": "neverOpen",
39 | "disableOptimisticBPs": true,
40 | "windows": {
41 | "program": "${workspaceFolder}/node_modules/jest/bin/jest"
42 | }
43 | }
44 | ]
45 | }
46 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false // set this to true to hide the "out" folder with the compiled JS files
5 | },
6 | "search.exclude": {
7 | "out": true // set this to false to include "out" folder in search results
8 | },
9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
10 | "typescript.tsc.autoDetect": "on",
11 | "typescript.tsdk": "node_modules\\typescript\\lib"
12 | }
13 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | out/test/**
4 | src/**
5 | .gitignore
6 | **/tsconfig.json
7 | **/*.map
8 | **/*.ts
9 | **/webpack.config.js
10 | website
11 | docs
12 | .babelrc
13 | cypress
--------------------------------------------------------------------------------
/.yarnrc:
--------------------------------------------------------------------------------
1 | network-timeout 600000
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Raathi Kugarajan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Paper is a note taking tool for VSCode. The motivation behind Paper is to create a tool that will let developers keep frequently accessed files and locations within files (functions, classes and etc) bookmarked for easy access.
7 |
8 |
9 |
10 | ### Install the extension
11 |
12 | You can install the [extension](https://marketplace.visualstudio.com/items?itemName=Raathigeshan.paper) from VSCode marketplace.
13 |
14 |
15 |
16 | ### Shortcuts
17 |
18 | - `# ` - Creates a H1 heading
19 | - `## ` - Creates a H2 heading
20 | - `### ` - Creates a H3 heading
21 | - `- ` - Creates a list
22 |
23 |
24 |
25 | ### Features
26 |
27 | #### Bookmark active file
28 |
29 |
30 |
31 | Click the file icon in the floating menu to create a bookmark for the active file.
32 |
33 | This is helpful if you want to bookmark a file that you frequently visit while implementing a feature.
34 |
35 |
36 |
37 | #### Bookmark selection
38 |
39 |
40 |
41 | Select text in a file and click the the pointer icon in the floating menu to create a bookmark to the selection.
42 |
43 | Using this feature you can bookmark any number of locations in your codebase you visit often.
44 |
45 |
46 |
47 | #### File tree
48 |
49 |
50 |
51 | A file tree widget shows you the files under a particular path as a tree view.
52 |
53 | This helps to keep a particular directory in-sight if don't want to scroll through in VSCode's tree view.
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/contributing.md
--------------------------------------------------------------------------------
/docs/assets/Paper File bookmark.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/docs/assets/Paper File bookmark.gif
--------------------------------------------------------------------------------
/docs/assets/Paper Filetree.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/docs/assets/Paper Filetree.gif
--------------------------------------------------------------------------------
/docs/assets/Paper Selection bookmark.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/docs/assets/Paper Selection bookmark.gif
--------------------------------------------------------------------------------
/docs/assets/PaperBanner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/docs/assets/PaperBanner.png
--------------------------------------------------------------------------------
/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/icons/icon.png
--------------------------------------------------------------------------------
/icons/tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/icons/tiny.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "paper",
3 | "displayName": "Paper",
4 | "description": "Take code notes",
5 | "publisher": "raathigeshan",
6 | "version": "0.11.0",
7 | "engines": {
8 | "vscode": "^1.50.0"
9 | },
10 | "categories": [
11 | "Other"
12 | ],
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/Raathigesh/paper"
16 | },
17 | "icon": "icons/icon.png",
18 | "galleryBanner": {
19 | "color": "#ffff",
20 | "theme": "light"
21 | },
22 | "activationEvents": [
23 | "*"
24 | ],
25 | "main": "./out/extension/extension",
26 | "contributes": {
27 | "viewsContainers": {
28 | "activitybar": [
29 | {
30 | "id": "paper-view",
31 | "title": "Paper",
32 | "icon": "icons/icon.png"
33 | }
34 | ]
35 | },
36 | "views": {
37 | "paper-view": [
38 | {
39 | "type": "webview",
40 | "id": "paper",
41 | "name": "Paper"
42 | }
43 | ]
44 | }
45 | },
46 | "scripts": {
47 | "vscode:prepublish": "yarn run build",
48 | "compile": "tsc -p ./",
49 | "watch": "tsc -watch -p ./src/extension --project ./tsconfig.json",
50 | "postinstall": "node ./node_modules/vscode/bin/install",
51 | "ui": "webpack-dev-server --env.development --config ./webpack.config.js",
52 | "build-ui": "webpack --env.production --config ./webpack.config.js",
53 | "build": "rimraf out && yarn compile && yarn build-ui",
54 | "package-ext": "vsce package --yarn",
55 | "publish-ext": "vsce publish --yarn",
56 | "pretty": "prettier --write \"src/**/*.{ts,tsx}\"",
57 | "test": "jest",
58 | "ship": "yarn version && git push origin --tag"
59 | },
60 | "devDependencies": {
61 | "@babel/core": "^7.7.2",
62 | "@babel/plugin-proposal-class-properties": "^7.4.0",
63 | "@babel/plugin-proposal-decorators": "^7.6.0",
64 | "@babel/plugin-proposal-object-rest-spread": "^7.4.3",
65 | "@babel/plugin-proposal-optional-chaining": "^7.6.0",
66 | "@babel/plugin-syntax-optional-chaining": "^7.2.0",
67 | "@babel/plugin-transform-runtime": "^7.5.0",
68 | "@babel/preset-env": "^7.4.3",
69 | "@babel/preset-react": "^7.0.0",
70 | "@babel/preset-typescript": "^7.7.2",
71 | "@chakra-ui/react": "^1.6.5",
72 | "@devtools-ds/dom-inspector": "^1.1.2",
73 | "@emotion/core": "^10.0.22",
74 | "@emotion/react": "11",
75 | "@emotion/styled": "11",
76 | "@tiptap/extension-link": "^2.0.0-beta.19",
77 | "@tiptap/react": "^2.0.0-beta.55",
78 | "@tiptap/starter-kit": "^2.0.0-beta.89",
79 | "@types/babel-traverse": "^6.25.5",
80 | "@types/cors": "^2.8.12",
81 | "@types/express": "^4.17.13",
82 | "@types/jest": "^24.0.21",
83 | "@types/mocha": "^2.2.42",
84 | "@types/node": "^10.12.21",
85 | "@types/react": "^16.8.14",
86 | "@types/react-dom": "^16.8.4",
87 | "@types/react-resizable": "^1.7.2",
88 | "@types/react-resize-detector": "^4.2.0",
89 | "@types/react-router-dom": "^4.3.3",
90 | "all-contributors-cli": "^6.17.0",
91 | "babel-loader": "^8.0.5",
92 | "babel-plugin-styled-components": "^1.10.0",
93 | "concurrently": "^5.2.0",
94 | "copy-to-clipboard": "^3.3.1",
95 | "cross-env": "^5.2.0",
96 | "css-loader": "^2.1.1",
97 | "emotion-theming": "^10.0.19",
98 | "file-loader": "^3.0.1",
99 | "framer-motion": "4",
100 | "html-webpack-plugin": "^3.2.0",
101 | "html-webpack-template": "^6.2.0",
102 | "husky": "^3.0.9",
103 | "jest": "^24.9.0",
104 | "prettier": "^1.19.1",
105 | "pretty-quick": "^2.0.0",
106 | "react": "^16.8.6",
107 | "react-dom": "^16.8.6",
108 | "react-feather": "^2.0.9",
109 | "rimraf": "^2.6.3",
110 | "style-loader": "^0.23.1",
111 | "terser-webpack-plugin": "^2.3.5",
112 | "typescript": "^4.1.3",
113 | "url-loader": "^3.0.0",
114 | "vscode": "^1.1.28",
115 | "vscode-test": "^1.4.0",
116 | "wait-on": "^5.1.0",
117 | "webpack": "^4.30.0",
118 | "webpack-cli": "^3.3.1",
119 | "webpack-dev-server": "^3.3.1"
120 | },
121 | "dependencies": {
122 | "body-parser": "^1.19.0",
123 | "cors": "^2.8.5",
124 | "directory-tree": "^2.2.9",
125 | "express": "^4.17.1",
126 | "get-port": "^5.1.1"
127 | },
128 | "husky": {
129 | "hooks": {
130 | "pre-commit": "pretty-quick --staged"
131 | }
132 | },
133 | "jest": {
134 | "testPathIgnorePatterns": [
135 | "/node_modules/",
136 | "/out/"
137 | ],
138 | "moduleNameMapper": {
139 | "^common(.*)$": "/src/common$1",
140 | "^entities(.*)$": "/src/entities$1",
141 | "^indexer(.*)$": "/src/indexer$1"
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | trailingComma: 'es5',
3 | tabWidth: 4,
4 | semi: true,
5 | singleQuote: true,
6 | };
7 |
--------------------------------------------------------------------------------
/src/extension/ContentProvider.ts:
--------------------------------------------------------------------------------
1 | export default class ContentProvider {
2 | getDevServerContent() {
3 | return `
4 |
5 |
6 |
7 |
8 |
9 | Waypoint
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | `;
18 | }
19 |
20 | getProdContent(port: number) {
21 | return `
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 | Waypoint
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | `;
40 | }
41 |
42 | getContent(port: number) {
43 | if (process.env.dev) {
44 | return this.getDevServerContent();
45 | }
46 |
47 | return this.getProdContent(port);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/extension/api/DocsManager.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | const StateKey = 'paper-documents';
4 |
5 | interface Document {
6 | content: string;
7 | id: string;
8 | type: 'doc';
9 | name: string;
10 | }
11 |
12 | interface State {
13 | activeDocument: null | string;
14 | documents: Document[];
15 | }
16 |
17 | export class DocsManager {
18 | context: vscode.ExtensionContext;
19 |
20 | constructor(context: vscode.ExtensionContext) {
21 | this.context = context;
22 | }
23 |
24 | getActiveDocument(): Document | null {
25 | if (this.context.workspaceState.get(StateKey) === undefined) {
26 | const newDocId = new Date().valueOf().toString();
27 | this.context.workspaceState.update(StateKey, {
28 | activeDocument: newDocId,
29 | documents: [
30 | {
31 | content:
32 | this.context.workspaceState.get('paper-content') ||
33 | '',
34 | id: newDocId,
35 | type: 'doc',
36 | name: 'Untitled',
37 | },
38 | ],
39 | } as State);
40 | }
41 |
42 | const state: State = this.context.workspaceState.get(StateKey) as any;
43 | const activeDocument = state.documents.find(
44 | item => item.id === state.activeDocument
45 | );
46 | if (!activeDocument) {
47 | return null;
48 | }
49 | return activeDocument;
50 | }
51 |
52 | setActiveDocument(id: string) {
53 | const state: State = this.context.workspaceState.get(StateKey) as any;
54 | this.context.workspaceState.update(StateKey, {
55 | ...state,
56 | activeDocument: id,
57 | } as State);
58 | }
59 |
60 | updateDocument(id: string, content: string) {
61 | const state: State = this.context.workspaceState.get(StateKey) as any;
62 | const updatedDocuments = state.documents.map(item => {
63 | if (item.id === id) {
64 | return {
65 | ...item,
66 | content,
67 | };
68 | }
69 | return item;
70 | });
71 |
72 | const nextState: State = {
73 | ...state,
74 | documents: updatedDocuments,
75 | };
76 | this.context.workspaceState.update(StateKey, nextState);
77 | }
78 |
79 | createDocument(name: string, content: string, type: 'doc') {
80 | const state: State = this.context.workspaceState.get(StateKey) as any;
81 | const id = new Date().valueOf().toString();
82 | const nextState: State = {
83 | ...state,
84 | documents: [
85 | ...state.documents,
86 | {
87 | id,
88 | content,
89 | type,
90 | name,
91 | },
92 | ],
93 | };
94 | this.context.workspaceState.update(StateKey, nextState);
95 |
96 | return id;
97 | }
98 |
99 | deleteDocument(id: string) {
100 | const state: State = this.context.workspaceState.get(StateKey) as any;
101 | const nextState: State = {
102 | ...state,
103 | activeDocument:
104 | id === state.activeDocument ? null : state.activeDocument,
105 | documents: state.documents.filter(item => item.id !== id),
106 | };
107 | this.context.workspaceState.update(StateKey, nextState);
108 | }
109 |
110 | getDocumentsList(): Document[] {
111 | const state: State = this.context.workspaceState.get(StateKey) as any;
112 | return state.documents;
113 | }
114 |
115 | renameDoc(id: string, name: string) {
116 | const state: State = this.context.workspaceState.get(StateKey) as any;
117 | const updatedDocuments = state.documents.map(item => {
118 | if (item.id === id) {
119 | return {
120 | ...item,
121 | name,
122 | };
123 | }
124 | return item;
125 | });
126 |
127 | const nextState: State = {
128 | ...state,
129 | documents: updatedDocuments,
130 | };
131 | this.context.workspaceState.update(StateKey, nextState);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/extension/api/index.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import * as vscode from 'vscode';
3 | import * as bodyParser from 'body-parser';
4 | import * as cors from 'cors';
5 | import * as dirTree from 'directory-tree';
6 |
7 | import { initializeStaticRoutes } from './static-files';
8 | import { isAbsolute, join, relative } from 'path';
9 | import { DocsManager } from './DocsManager';
10 | import { resolvePath } from './utils';
11 |
12 | export async function startApiServer(
13 | port: number,
14 | context: vscode.ExtensionContext
15 | ) {
16 | const docsManager = new DocsManager(context);
17 |
18 | const app = express();
19 | app.use(bodyParser());
20 | app.use(cors());
21 | initializeStaticRoutes(app, port);
22 |
23 | app.post('/open-file', (req, res) => {
24 | const path = req.body.filePath;
25 | const fullPath = resolvePath(vscode.workspace.rootPath || '', path);
26 |
27 | let selection = undefined;
28 | if (req.body.selection) {
29 | selection = new vscode.Selection(
30 | new vscode.Position(
31 | req.body.selection.start.line,
32 | req.body.selection.start.column
33 | ),
34 | new vscode.Position(
35 | req.body.selection.end.line,
36 | req.body.selection.end.column
37 | )
38 | );
39 | }
40 | vscode.window.showTextDocument(vscode.Uri.file(fullPath), {
41 | selection,
42 | });
43 | res.send('OK');
44 | });
45 |
46 | app.post('/create', (req, res) => {
47 | const name = req.body.name;
48 | const type = req.body.type;
49 |
50 | const id = docsManager.createDocument(name, '', type);
51 | const documents = docsManager.getDocumentsList();
52 | res.json({
53 | id,
54 | documents,
55 | });
56 | });
57 |
58 | app.get('/content', (req, res) => {
59 | const document = docsManager.getActiveDocument();
60 | res.json(document);
61 | });
62 |
63 | app.post('/content', (req, res) => {
64 | const content = req.body.content;
65 | const id = req.body.id;
66 |
67 | if (!id) {
68 | res.sendStatus(500);
69 | } else {
70 | docsManager.updateDocument(id, content);
71 | res.send('OK');
72 | }
73 | });
74 |
75 | app.get('/documents', (req, res) => {
76 | const documents = docsManager.getDocumentsList();
77 | res.json(documents);
78 | });
79 |
80 | app.post('/changeActiveDocument', (req, res) => {
81 | const id = req.body.id;
82 | docsManager.setActiveDocument(id);
83 | res.send('OK');
84 | });
85 |
86 | app.post('/renameDocument', (req, res) => {
87 | const id = req.body.id;
88 | const newName = req.body.newName;
89 |
90 | docsManager.renameDoc(id, newName);
91 | res.send('OK');
92 | });
93 |
94 | app.post('/deleteDocument', (req, res) => {
95 | const id = req.body.id;
96 | docsManager.deleteDocument(id);
97 |
98 | res.send('OK');
99 | });
100 |
101 | app.post('/tree', (req, res) => {
102 | const fullPath = resolvePath(
103 | vscode.workspace.rootPath || '',
104 | req.body.directoryPath
105 | );
106 | const tree = dirTree(fullPath);
107 | res.json(tree);
108 | });
109 |
110 | app.get('/events', async function(req, res) {
111 | res.set({
112 | 'Cache-Control': 'no-cache',
113 | 'Content-Type': 'text/event-stream',
114 | Connection: 'keep-alive',
115 | });
116 | res.flushHeaders();
117 |
118 | // Tell the client to retry every 10 seconds if connectivity is lost
119 | res.write('retry: 10000\n\n');
120 |
121 | vscode.window.onDidChangeActiveTextEditor(event => {
122 | if (event) {
123 | res.write(
124 | `data: ${JSON.stringify({
125 | activeEditor: event?.document.fileName,
126 | })}\n\n`
127 | );
128 | }
129 | });
130 | });
131 |
132 | // View specific endpoints
133 | app.get('/activeFilePath', (req, res) => {
134 | res.json({
135 | activeFilePath: relative(
136 | vscode.workspace.rootPath || '',
137 | vscode.window.activeTextEditor?.document.fileName || ''
138 | ),
139 | });
140 | });
141 |
142 | app.get('/getSelection', (req, res) => {
143 | if (vscode.window.activeTextEditor?.selection.start) {
144 | const selectionRange = new vscode.Range(
145 | vscode.window.activeTextEditor?.selection.start,
146 | vscode.window.activeTextEditor?.selection.end
147 | );
148 | const text = vscode.window.activeTextEditor?.document.getText(
149 | selectionRange
150 | );
151 |
152 | const fullTxt = `${text}|${vscode.window.activeTextEditor?.document.fileName}|${selectionRange.start.line}|${selectionRange.start.character}|${selectionRange.end.line}|${selectionRange.end.character}`;
153 | res.json({
154 | selection: fullTxt,
155 | });
156 | }
157 |
158 | res.json({
159 | selection: null,
160 | });
161 | });
162 |
163 | return new Promise((resolve, reject) => {
164 | app.listen(port, () => {
165 | const url = `http://localhost:${port}`;
166 | console.log(`⚡ Insight is running at ${url} `);
167 | resolve(null);
168 | });
169 | });
170 | }
171 |
--------------------------------------------------------------------------------
/src/extension/api/static-files.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import { readdirSync } from 'fs';
3 |
4 | export function initializeStaticRoutes(express: any, port: number) {
5 | const uiDirectory = join(__dirname, '../../ui');
6 | const files = readdirSync(uiDirectory);
7 |
8 | const htmlContent = `
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 | Waypoint
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | `;
27 |
28 | express.get('/', (req: any, res: any) => {
29 | res.set('Content-Type', 'text/html');
30 | res.send(htmlContent);
31 | });
32 |
33 | files.forEach(fileName => {
34 | express.get(`/${fileName}`, (req: any, res: any) => {
35 | return res.sendFile(join(uiDirectory, fileName));
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/src/extension/api/utils.ts:
--------------------------------------------------------------------------------
1 | import { isAbsolute, join, normalize, resolve } from 'path';
2 |
3 | export function resolvePath(workspaceRoot: string, path: string) {
4 | if (resolve(path) === normalize(path)) {
5 | return path;
6 | }
7 |
8 | return join(workspaceRoot, path);
9 | }
10 |
--------------------------------------------------------------------------------
/src/extension/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import ContentProvider from './ContentProvider';
3 | import { startApiServer } from './api';
4 | import { join } from 'path';
5 | const getPort = require('get-port');
6 |
7 | let isServerRunning = false;
8 | let port = 0;
9 |
10 | export function activate(context: vscode.ExtensionContext) {
11 | initialize(context);
12 | }
13 |
14 | export function deactivate() {}
15 |
16 | async function initialize(context: vscode.ExtensionContext) {
17 | if (vscode.workspace.rootPath) {
18 | process.env.projectRoot = vscode.workspace.rootPath;
19 | }
20 |
21 | if (!isServerRunning) {
22 | port = await getPort({ port: 4545 });
23 | await startApiServer(port, context);
24 | isServerRunning = true;
25 | }
26 |
27 | const provider = new WaypointViewProvider();
28 | context.subscriptions.push(
29 | vscode.window.registerWebviewViewProvider(
30 | WaypointViewProvider.viewType,
31 | provider
32 | )
33 | );
34 | }
35 |
36 | class WaypointViewProvider implements vscode.WebviewViewProvider {
37 | public static readonly viewType = 'paper';
38 |
39 | private _view?: vscode.WebviewView;
40 |
41 | constructor() {}
42 |
43 | public resolveWebviewView(
44 | webviewView: vscode.WebviewView,
45 | context: vscode.WebviewViewResolveContext,
46 | _token: vscode.CancellationToken
47 | ) {
48 | this._view = webviewView;
49 |
50 | webviewView.webview.options = {
51 | // Allow scripts in the webview
52 | enableScripts: true,
53 | };
54 |
55 | const contentProvider = new ContentProvider();
56 | webviewView.webview.html = contentProvider.getContent(port);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/extension/typings.d.ts:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/ui/App.tsx:
--------------------------------------------------------------------------------
1 | import { ChakraProvider, Flex, GlobalStyle } from '@chakra-ui/react';
2 | import React, { useCallback, useEffect, useState } from 'react';
3 | import { Grid } from 'react-feather';
4 | import Editor from './Editor';
5 | import { ClientDoc } from './types';
6 | import DocList from './docs-list';
7 | import { theme } from './theme';
8 |
9 | const API_URL = `http://localhost:${(window as any).port || '4545'}`;
10 |
11 | function App() {
12 | const [activeDoc, setActiveDoc] = useState(null);
13 |
14 | const updateContent = useCallback(
15 | async (content: string) => {
16 | if (activeDoc === null) {
17 | return;
18 | }
19 |
20 | fetch(`${API_URL}/content`, {
21 | method: 'POST',
22 | headers: {
23 | 'Content-Type': 'application/json',
24 | },
25 | body: JSON.stringify({
26 | content,
27 | id: activeDoc.id,
28 | }),
29 | });
30 | },
31 | [activeDoc]
32 | );
33 |
34 | async function getContent() {
35 | const document: ClientDoc = await (
36 | await fetch(`${API_URL}/content`)
37 | ).json();
38 | setActiveDoc(document);
39 | }
40 |
41 | useEffect(() => {
42 | getContent();
43 | }, [setActiveDoc]);
44 |
45 | return (
46 |
47 |
48 |
49 |
50 |
59 |
60 | {activeDoc && activeDoc.name}
61 |
62 | {
65 | getContent();
66 | }}
67 | />
68 |
69 |
70 |
74 |
75 | {!activeDoc && (
76 |
82 | No notes found. Create a note to begin.
83 |
84 | )}
85 |
86 |
87 | );
88 | }
89 |
90 | export default App;
91 |
--------------------------------------------------------------------------------
/src/ui/Editor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useEditor, EditorContent, FloatingMenu } from '@tiptap/react';
3 | import StarterKit from '@tiptap/starter-kit';
4 | import fileBookmark from './editor-views/file-bookmark';
5 | import treeView from './editor-views/file-tree';
6 | import selectionBookmark from './editor-views/selection-bookmark';
7 | import './styles.css';
8 | import { Flex } from '@chakra-ui/react';
9 | import { File, MousePointer, Folder } from 'react-feather';
10 | import { EditorFloatingButton } from './EditorFloatingButton';
11 |
12 | const API_URL = `http://localhost:${(window as any).port || '4545'}`;
13 |
14 | interface Props {
15 | content: string;
16 | onChange: (content: string) => void;
17 | }
18 |
19 | const Editor = ({ content, onChange }: Props) => {
20 | const editor = useEditor({
21 | extensions: [StarterKit, fileBookmark, treeView, selectionBookmark],
22 | content: '',
23 | });
24 |
25 | useEffect(() => {
26 | const handler = ({ editor }: any) => {
27 | onChange(editor.getHTML());
28 | };
29 | editor?.on('update', handler);
30 | return () => {
31 | editor?.off('update', handler);
32 | };
33 | }, [editor, onChange]);
34 |
35 | useEffect(() => {
36 | editor?.commands.setContent(content);
37 | }, [content, editor]);
38 |
39 | return (
40 | <>
41 | {editor && (
42 |
43 |
44 | {
47 | const focusResult = editor.chain().focus();
48 | (focusResult as any)
49 | .toggleReactComponent()
50 | .run();
51 | }}
52 | icon={}
53 | />
54 |
59 | {
62 | const focusResult = editor.chain().focus();
63 | (focusResult as any).toggleRange().run();
64 | }}
65 | icon={
66 |
67 | }
68 | />
69 |
70 | {
73 | const focusResult = editor.chain().focus();
74 | (focusResult as any).toggleTreeView().run();
75 | }}
76 | icon={}
77 | />
78 |
79 |
80 | )}
81 |
82 | >
83 | );
84 | };
85 |
86 | export default Editor;
87 |
--------------------------------------------------------------------------------
/src/ui/EditorFloatingButton.tsx:
--------------------------------------------------------------------------------
1 | import { Tooltip, Button } from '@chakra-ui/react';
2 | import React from 'react';
3 |
4 | interface Props {
5 | onClick: () => void;
6 | tooltip: string;
7 | icon: any;
8 | }
9 |
10 | export function EditorFloatingButton({ onClick, tooltip, icon }: Props) {
11 | return (
12 |
13 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/ui/EventsListener.ts:
--------------------------------------------------------------------------------
1 | const API_URL = `http://localhost:${(window as any).port || '4545'}`;
2 |
3 | class Listener {
4 | source: EventSource;
5 | listeners: ((path: string) => void)[] = [];
6 |
7 | constructor() {
8 | this.source = new EventSource(`${API_URL}/events`);
9 |
10 | this.source.addEventListener('message', message => {
11 | const dataObj = JSON.parse(message.data);
12 | this.listeners.forEach(cb => {
13 | console.log(dataObj);
14 | cb(dataObj.activeEditor);
15 | });
16 | });
17 | }
18 |
19 | addListener(cb: (path: string) => void) {
20 | this.listeners.push(cb);
21 | }
22 | }
23 |
24 | export default new Listener();
25 |
--------------------------------------------------------------------------------
/src/ui/assets/JsBubblesLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/src/ui/assets/JsBubblesLogo.png
--------------------------------------------------------------------------------
/src/ui/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/src/ui/assets/logo.png
--------------------------------------------------------------------------------
/src/ui/docs-list/DocItem.tsx:
--------------------------------------------------------------------------------
1 | import { Flex, Input, Tooltip, useTheme } from '@chakra-ui/react';
2 | import React, { useState } from 'react';
3 | import { Edit, Check, Trash } from 'react-feather';
4 | import { ClientDoc } from '../types';
5 |
6 | interface Props {
7 | isActive: boolean;
8 | onClick: () => void;
9 | doc: ClientDoc;
10 | onRename: (id: string, newName: string) => void;
11 | onDelete: (id: string) => void;
12 | }
13 |
14 | export function DocItem({ isActive, onClick, doc, onRename, onDelete }: Props) {
15 | const [isEditMode, setIsEditMode] = useState(false);
16 | const [docName, setDocName] = useState(doc.name);
17 |
18 | return (
19 | {
29 | onClick();
30 | }}
31 | height="30px"
32 | marginBottom="3px"
33 | >
34 |
35 | {isEditMode && (
36 |
41 | {
47 | if (e.key == 'Enter') {
48 | setIsEditMode(false);
49 | onRename(doc.id, docName);
50 | }
51 | }}
52 | size="small"
53 | value={docName}
54 | onChange={e => setDocName(e.target.value)}
55 | />
56 | {
63 | setIsEditMode(false);
64 | onRename(doc.id, docName);
65 | }}
66 | >
67 |
68 |
69 |
70 | )}
71 | {!isEditMode && (
72 |
73 | {doc.name || doc.id}
74 |
75 | setIsEditMode(true)}
77 | marginRight="10px"
78 | _hover={{
79 | color: 'brand.400',
80 | }}
81 | >
82 |
83 |
84 |
85 | {
88 | if (!isActive) {
89 | onDelete(doc.id);
90 | }
91 | e.stopPropagation();
92 | }}
93 | cursor={isActive ? 'not-allowed' : 'pointer'}
94 | _hover={{
95 | color: isActive ? 'none' : 'brand.400',
96 | }}
97 | >
98 |
105 |
106 |
107 |
108 |
109 |
110 | )}
111 |
112 |
113 | );
114 | }
115 |
--------------------------------------------------------------------------------
/src/ui/docs-list/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Flex,
4 | Input,
5 | Popover,
6 | PopoverArrow,
7 | PopoverBody,
8 | PopoverCloseButton,
9 | PopoverContent,
10 | PopoverFooter,
11 | PopoverHeader,
12 | PopoverTrigger,
13 | Portal,
14 | } from '@chakra-ui/react';
15 | import React, { useEffect, useState } from 'react';
16 | import { Menu, ArrowRight } from 'react-feather';
17 | import { ClientDoc } from '../types';
18 | import { DocItem } from './DocItem';
19 |
20 | const API_URL = `http://localhost:${(window as any).port || '4545'}`;
21 |
22 | interface Props {
23 | activeDoc: ClientDoc | null;
24 | onActiveDocumentChange: () => void;
25 | }
26 |
27 | export default function CreateDoc({
28 | activeDoc,
29 | onActiveDocumentChange,
30 | }: Props) {
31 | const [docs, setDocs] = useState([]);
32 | const [docName, setDocName] = useState('');
33 |
34 | const getDocs = async () => {
35 | const response = await fetch(`${API_URL}/documents`);
36 | const documents = await response.json();
37 | setDocs(documents);
38 | };
39 |
40 | const changeActiveDocument = async (id: string) => {
41 | await fetch(`${API_URL}/changeActiveDocument`, {
42 | method: 'POST',
43 | headers: {
44 | 'Content-Type': 'application/json',
45 | },
46 | body: JSON.stringify({
47 | id,
48 | }),
49 | });
50 |
51 | onActiveDocumentChange();
52 | };
53 |
54 | const createDoc = async (name: string) => {
55 | const response = await fetch(`${API_URL}/create`, {
56 | method: 'POST',
57 | headers: {
58 | 'Content-Type': 'application/json',
59 | },
60 | body: JSON.stringify({
61 | name,
62 | type: 'doc',
63 | }),
64 | });
65 | const { documents, id } = await response.json();
66 | setDocs(documents);
67 | changeActiveDocument(id);
68 | };
69 |
70 | const renameDocument = async (id: string, newName: string) => {
71 | await fetch(`${API_URL}/renameDocument`, {
72 | method: 'POST',
73 | headers: {
74 | 'Content-Type': 'application/json',
75 | },
76 | body: JSON.stringify({
77 | id,
78 | newName,
79 | }),
80 | });
81 |
82 | getDocs();
83 | };
84 |
85 | const deleteDoc = async (id: string) => {
86 | await fetch(`${API_URL}/deleteDocument`, {
87 | method: 'POST',
88 | headers: {
89 | 'Content-Type': 'application/json',
90 | },
91 | body: JSON.stringify({
92 | id,
93 | }),
94 | });
95 |
96 | getDocs();
97 | };
98 |
99 | return (
100 | getDocs()}
103 | arrowShadowColor="brand.300"
104 | >
105 |
106 |
113 |
114 |
115 |
116 |
117 |
128 |
133 | Docs
134 |
135 |
136 | {docs.map(item => (
137 | {
144 | changeActiveDocument(item.id);
145 | }}
146 | onDelete={deleteDoc}
147 | onRename={renameDocument}
148 | />
149 | ))}
150 | {docs.length === 0 && (
151 | No documents found
152 | )}
153 |
154 |
155 |
156 | setDocName(e.target.value)}
171 | onKeyDown={e => {
172 | if (e.key === 'Enter') {
173 | createDoc(docName);
174 | setDocName('');
175 | }
176 | }}
177 | />
178 |
199 |
200 |
201 |
202 |
203 |
204 | );
205 | }
206 |
--------------------------------------------------------------------------------
/src/ui/editor-views/file-bookmark/Component.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { NodeViewWrapper } from '@tiptap/react';
3 | import { Flex, Tooltip } from '@chakra-ui/react';
4 | import { Trash } from 'react-feather';
5 | import './styles.css';
6 |
7 | const API_URL = `http://localhost:${(window as any).port || '4545'}`;
8 |
9 | export default (props: any) => {
10 | const setPath = (bookmark: string) => {
11 | props.updateAttributes({
12 | path: bookmark,
13 | });
14 | };
15 |
16 | const getActivePath = async () => {
17 | if (props.node.attrs.path === '') {
18 | const response = await fetch(`${API_URL}/activeFilePath`);
19 | const data = await response.json();
20 | if (data.activeFilePath) {
21 | setPath(data.activeFilePath);
22 | }
23 | }
24 | };
25 |
26 | useEffect(() => {
27 | getActivePath();
28 | }, []);
29 |
30 | const openFile = (path: string) => {
31 | fetch(`${API_URL}/open-file`, {
32 | method: 'POST',
33 | headers: {
34 | 'Content-Type': 'application/json',
35 | },
36 | body: JSON.stringify({
37 | filePath: path,
38 | }),
39 | });
40 | };
41 |
42 | return (
43 |
44 | openFile(props.node.attrs.path)}
55 | >
56 |
57 | {props.node.attrs.path}
58 |
59 | {
67 | e.stopPropagation();
68 | props.deleteNode();
69 | }}
70 | marginLeft="5px"
71 | height="100%"
72 | alignItems="center"
73 | >
74 |
75 |
76 |
77 |
78 |
79 |
80 | );
81 | };
82 |
--------------------------------------------------------------------------------
/src/ui/editor-views/file-bookmark/index.tsx:
--------------------------------------------------------------------------------
1 | import { Node, mergeAttributes } from '@tiptap/core';
2 | import { ReactNodeViewRenderer } from '@tiptap/react';
3 | import Component from './Component';
4 |
5 | export default Node.create({
6 | name: 'bookmark',
7 |
8 | group: 'inline',
9 |
10 | inline: true,
11 |
12 | atom: true,
13 |
14 | addAttributes() {
15 | return {
16 | path: {
17 | default: '',
18 | },
19 | };
20 | },
21 |
22 | parseHTML() {
23 | return [
24 | {
25 | tag: 'bookmark',
26 | },
27 | ];
28 | },
29 |
30 | renderHTML({ HTMLAttributes }) {
31 | return ['bookmark', mergeAttributes(HTMLAttributes)];
32 | },
33 |
34 | addNodeView() {
35 | return ReactNodeViewRenderer(Component);
36 | },
37 |
38 | addCommands() {
39 | return {
40 | toggleReactComponent: () => ({ chain }: any) => {
41 | return chain()
42 | .insertContent('')
43 | .run();
44 | },
45 | } as any;
46 | },
47 | });
48 |
--------------------------------------------------------------------------------
/src/ui/editor-views/file-bookmark/styles.css:
--------------------------------------------------------------------------------
1 | .ProseMirror .bookmarkRenderer {
2 | display: inline-block;
3 | }
4 |
--------------------------------------------------------------------------------
/src/ui/editor-views/file-tree/Component.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { NodeViewWrapper } from '@tiptap/react';
3 | import {
4 | Input,
5 | Box,
6 | IconButton,
7 | Flex,
8 | Button,
9 | Tooltip,
10 | } from '@chakra-ui/react';
11 | import { Edit, Check, RefreshCw, Trash2 } from 'react-feather';
12 | import { Node } from './Node';
13 | import './styles.css';
14 | import listener from '../../EventsListener';
15 |
16 | const API_URL = `http://localhost:${(window as any).port || '4545'}`;
17 |
18 | export default (props: any) => {
19 | const [data, setData] = useState(null);
20 | const [selectedEditor, setSelectedEditor] = useState(null);
21 | const [collapsedNodes, setCollapsedNodes] = useState(
22 | JSON.parse(props.node.attrs.collapsedNodes)
23 | );
24 |
25 | useEffect(() => {
26 | const cb = (path: string) => {
27 | setSelectedEditor(path);
28 | };
29 |
30 | listener.addListener(cb);
31 | }, []);
32 |
33 | const getTree = async (path: string) => {
34 | const response = await fetch(`${API_URL}/tree`, {
35 | method: 'POST',
36 | headers: {
37 | 'Content-Type': 'application/json',
38 | },
39 | body: JSON.stringify({
40 | directoryPath: path,
41 | }),
42 | });
43 |
44 | const tree = await response.json();
45 |
46 | setData(tree);
47 | };
48 |
49 | const openFile = (path: string) => {
50 | fetch(`${API_URL}/open-file`, {
51 | method: 'POST',
52 | headers: {
53 | 'Content-Type': 'application/json',
54 | },
55 | body: JSON.stringify({
56 | filePath: path,
57 | }),
58 | });
59 | };
60 |
61 | const setCollapsedNode = (path: string, value: boolean) => {
62 | setCollapsedNodes({
63 | ...collapsedNodes,
64 | [path]: value,
65 | });
66 | props.updateAttributes({
67 | collapsedNodes: JSON.stringify({
68 | ...collapsedNodes,
69 | [path]: value,
70 | }),
71 | });
72 | };
73 |
74 | useEffect(() => {
75 | if (props.node.attrs.path !== '') {
76 | getTree(props.node.attrs.path);
77 | }
78 | }, []);
79 |
80 | const setPath = (bookmark: string) => {
81 | props.updateAttributes({
82 | path: bookmark,
83 | });
84 | };
85 |
86 | return (
87 |
91 |
99 |
100 | setPath(e.target.value)}
102 | value={props.node.attrs.path}
103 | borderColor="brand.300"
104 | color="brand.600"
105 | size="sm"
106 | backgroundColor="brand.300"
107 | _hover={{ border: '1px solid brand.500' }}
108 | borderRadius="4px"
109 | placeholder="Paste a directory path here"
110 | />
111 |
112 |
124 |
125 |
126 |
137 |
138 |
139 |
145 | {data && (
146 | {
151 | openFile(path);
152 | }}
153 | onCollapse={setCollapsedNode}
154 | />
155 | )}
156 |
157 |
158 |
159 | );
160 | };
161 |
--------------------------------------------------------------------------------
/src/ui/editor-views/file-tree/Node.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Flex } from '@chakra-ui/react';
2 | import React, { useState } from 'react';
3 | import { File, Folder, ChevronDown, ChevronRight } from 'react-feather';
4 |
5 | interface Props {
6 | name: string;
7 | type: 'file' | 'directory';
8 | path: string;
9 | children: Props[];
10 | collapsedNodes: { [key: string]: boolean };
11 | onFileClick: (path: string) => void;
12 | onCollapse: (path: string, value: boolean) => void;
13 | selectedEditor: string | null;
14 | }
15 |
16 | export function Node({
17 | name,
18 | type,
19 | path,
20 | onFileClick,
21 | collapsedNodes,
22 | onCollapse,
23 | children = [],
24 | selectedEditor,
25 | }: Props) {
26 | const isExpanded =
27 | collapsedNodes[path] === undefined ? true : collapsedNodes[path];
28 |
29 | const ArrowIcon =
30 | type === 'directory' ? (
31 | isExpanded ? (
32 |
33 | ) : (
34 |
35 | )
36 | ) : null;
37 | const Icon = type === 'file' ? File : Folder;
38 |
39 | const isActive = selectedEditor?.toLowerCase() === path.toLowerCase();
40 | return (
41 |
42 | {
54 | e.stopPropagation();
55 | if (type === 'file') {
56 | onFileClick(path);
57 | } else {
58 | onCollapse(path, !isExpanded);
59 | }
60 | }}
61 | >
62 | {ArrowIcon}
63 |
64 |
65 | {name}
66 |
67 |
68 | {isExpanded &&
69 | children.map(child => (
70 |
77 | ))}
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/ui/editor-views/file-tree/index.tsx:
--------------------------------------------------------------------------------
1 | import { Node, mergeAttributes } from '@tiptap/core';
2 | import { ReactNodeViewRenderer } from '@tiptap/react';
3 | import Component from './Component';
4 |
5 | export default Node.create({
6 | name: 'treeview',
7 |
8 | group: 'block',
9 |
10 | atom: true,
11 |
12 | addAttributes() {
13 | return {
14 | path: {
15 | default: '',
16 | },
17 | collapsedNodes: {
18 | default: '{}',
19 | },
20 | };
21 | },
22 |
23 | parseHTML() {
24 | return [
25 | {
26 | tag: 'treeview',
27 | },
28 | ];
29 | },
30 |
31 | renderHTML({ HTMLAttributes }) {
32 | return ['treeview', mergeAttributes(HTMLAttributes)];
33 | },
34 |
35 | addNodeView() {
36 | return ReactNodeViewRenderer(Component);
37 | },
38 |
39 | addCommands() {
40 | return {
41 | toggleTreeView: () => ({ chain }: any) => {
42 | return chain()
43 | .insertContent('')
44 | .run();
45 | },
46 | } as any;
47 | },
48 | });
49 |
--------------------------------------------------------------------------------
/src/ui/editor-views/file-tree/styles.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Raathigesh/paper/4011f87dbdb815259f87708cdbb845043ad39d37/src/ui/editor-views/file-tree/styles.css
--------------------------------------------------------------------------------
/src/ui/editor-views/selection-bookmark/Component.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { NodeViewWrapper } from '@tiptap/react';
3 | import {
4 | Input,
5 | Box,
6 | IconButton,
7 | Flex,
8 | Modal,
9 | ModalOverlay,
10 | ModalContent,
11 | ModalHeader,
12 | ModalBody,
13 | ModalFooter,
14 | Button,
15 | } from '@chakra-ui/react';
16 | import { Edit, Check, Trash } from 'react-feather';
17 | import './styles.css';
18 |
19 | const API_URL = `http://localhost:${(window as any).port || '4545'}`;
20 |
21 | export default (props: any) => {
22 | const setPath = (bookmark: string) => {
23 | props.updateAttributes({
24 | path: bookmark,
25 | });
26 | };
27 |
28 | const getActivePath = async () => {
29 | if (props.node.attrs.path === '') {
30 | const response = await fetch(`${API_URL}/getSelection`);
31 | const data = await response.json();
32 | if (data.selection) {
33 | setPath(data.selection);
34 | }
35 | }
36 | };
37 |
38 | useEffect(() => {
39 | getActivePath();
40 | }, []);
41 |
42 | const [
43 | name,
44 | path,
45 | startLine,
46 | startChar,
47 | endLine,
48 | endChar,
49 | ] = props.node.attrs.path.split('|');
50 |
51 | const openFile = (path: string) => {
52 | fetch(`${API_URL}/open-file`, {
53 | method: 'POST',
54 | headers: {
55 | 'Content-Type': 'application/json',
56 | },
57 | body: JSON.stringify({
58 | filePath: path,
59 | selection: {
60 | start: {
61 | line: Number(startLine),
62 | column: Number(startChar),
63 | },
64 | end: {
65 | line: Number(endLine),
66 | column: Number(endChar),
67 | },
68 | },
69 | }),
70 | });
71 | };
72 |
73 | return (
74 |
75 | openFile(path)}
86 | >
87 |
88 | {name}
89 |
90 | {
98 | e.stopPropagation();
99 | props.deleteNode();
100 | }}
101 | marginLeft="5px"
102 | height="100%"
103 | alignItems="center"
104 | >
105 |
106 |
107 |
108 |
109 | );
110 | };
111 |
--------------------------------------------------------------------------------
/src/ui/editor-views/selection-bookmark/index.tsx:
--------------------------------------------------------------------------------
1 | import { Node, mergeAttributes } from '@tiptap/core';
2 | import { ReactNodeViewRenderer } from '@tiptap/react';
3 | import Component from './Component';
4 |
5 | export default Node.create({
6 | name: 'range',
7 |
8 | group: 'inline',
9 |
10 | inline: true,
11 |
12 | atom: true,
13 |
14 | addAttributes() {
15 | return {
16 | path: {
17 | default: '',
18 | },
19 | };
20 | },
21 |
22 | parseHTML() {
23 | return [
24 | {
25 | tag: 'range',
26 | },
27 | ];
28 | },
29 |
30 | renderHTML({ HTMLAttributes }) {
31 | return ['range', mergeAttributes(HTMLAttributes)];
32 | },
33 |
34 | addNodeView() {
35 | return ReactNodeViewRenderer(Component);
36 | },
37 |
38 | addCommands() {
39 | return {
40 | toggleRange: () => ({ chain }: any) => {
41 | return chain()
42 | .insertContent('')
43 | .run();
44 | },
45 | } as any;
46 | },
47 | });
48 |
--------------------------------------------------------------------------------
/src/ui/editor-views/selection-bookmark/styles.css:
--------------------------------------------------------------------------------
1 | .ProseMirror .bookmarkRenderer {
2 | display: inline-block;
3 | }
4 |
--------------------------------------------------------------------------------
/src/ui/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import App from './App';
4 |
5 | render(, document.getElementById('root'));
6 |
7 | if ((module as any).hot) {
8 | (module as any).hot.accept('./App', () => {
9 | const NextApp = require('./App').default;
10 | render(, document.getElementById('root'));
11 | });
12 | }
13 |
--------------------------------------------------------------------------------
/src/ui/styles.css:
--------------------------------------------------------------------------------
1 | .ProseMirror {
2 | height: calc(95vh);
3 | border: none;
4 | margin: 10px;
5 | padding: 5px;
6 | }
7 |
8 | .ProseMirror-focused {
9 | outline: none;
10 | }
11 |
--------------------------------------------------------------------------------
/src/ui/theme.ts:
--------------------------------------------------------------------------------
1 | import { extendTheme } from '@chakra-ui/react';
2 |
3 | export const theme = extendTheme({
4 | styles: {
5 | global: {
6 | 'html, body': {
7 | padding: '0px',
8 | backgroundColor: 'brand.500',
9 | color: 'white',
10 | caretColor: 'white',
11 | fontSize: '15px',
12 | },
13 | h1: {
14 | fontSize: '25px',
15 | fontWeight: 300,
16 | },
17 | h2: {
18 | fontSize: '20px',
19 | fontWeight: 300,
20 | },
21 | h3: {
22 | fontSize: '18px',
23 | fontWeight: 300,
24 | },
25 | h4: {
26 | fontSize: '15px',
27 | fontWeight: 300,
28 | },
29 | ul: {
30 | marginLeft: '15px',
31 | },
32 | li: {
33 | marginLeft: '15px',
34 | },
35 | button: {
36 | margin: '3px',
37 | },
38 | ':focus': {
39 | outline: 'none',
40 | },
41 | },
42 | },
43 | colors: {
44 | brand: {
45 | 100: '#090909',
46 | 200: '#272727',
47 | 300: '#2F2E31',
48 | 400: '#FFB800',
49 | 500: '#1C1C1E',
50 | 600: '#f1f0ee',
51 | 700: '#25252e',
52 | 800: '#8bc34a',
53 | 900: '#e91e63',
54 | },
55 | },
56 | });
57 |
--------------------------------------------------------------------------------
/src/ui/types.ts:
--------------------------------------------------------------------------------
1 | export interface ClientDoc {
2 | content: string;
3 | id: string;
4 | type: 'doc';
5 | name: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/ui/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'react-color';
2 | declare module 'react-tippy';
3 | declare module 'react-virtualized-auto-sizer';
4 | declare module 'react-select/creatable';
5 | declare module 'react-select/async';
6 | declare module 'lodash.debounce';
7 | declare module '*.gql' {
8 | const content: any;
9 | export default content;
10 | }
11 | declare module 'redux-persist/lib/integration/react';
12 | declare module '*.png';
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": ["es6", "dom", "es2017.object", "esnext.asynciterable"],
7 | "sourceMap": true,
8 | "rootDir": "src",
9 | "strict": true,
10 | "allowSyntheticDefaultImports": true,
11 | "jsx": "react",
12 | "skipLibCheck": true,
13 | "experimentalDecorators": true,
14 | "emitDecoratorMetadata": true,
15 | "resolveJsonModule": true,
16 | "baseUrl": "./src",
17 | "paths": {
18 | "entities": ["./entities"],
19 | "indexer": ["./indexer"],
20 | "common": ["./common"]
21 | }
22 | },
23 | "include": ["src"]
24 | }
25 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const TerserPlugin = require('terser-webpack-plugin');
3 | const webpack = require('webpack');
4 | const path = require('path');
5 |
6 | module.exports = env => ({
7 | entry: {
8 | ui: './src/ui/index.tsx',
9 | },
10 | mode: env.production ? 'production' : 'development',
11 | output: {
12 | path: path.resolve(__dirname, './out/ui'),
13 | filename: '[name].bundle.js',
14 | },
15 | resolve: {
16 | extensions: ['.mjs', '.ts', '.tsx', '.js', '.jsx'],
17 | alias: {
18 | common: path.resolve(__dirname, 'src/common'),
19 | ui: path.resolve(__dirname, 'src/ui'),
20 | },
21 | },
22 | devServer: {
23 | contentBase: path.resolve(__dirname, '../dist/ui'),
24 | hot: true,
25 | port: 9000,
26 | disableHostCheck: true,
27 | headers: {
28 | 'Access-Control-Allow-Origin': '*',
29 | },
30 | },
31 | optimization: {
32 | minimizer: [
33 | new TerserPlugin({
34 | cache: true,
35 | parallel: true,
36 | sourceMap: true,
37 | terserOptions: {
38 | compress: {
39 | inline: false,
40 | },
41 | },
42 | }),
43 | ],
44 | },
45 | devtool: 'source-map',
46 | module: {
47 | rules: [
48 | {
49 | test: /\.(js|jsx|ts|tsx)$/,
50 | exclude: /(node_modules)/,
51 | loader: 'babel-loader',
52 | },
53 | {
54 | test: /\.(png|svg|jpg|gif)$/,
55 | use: [
56 | {
57 | loader: 'url-loader',
58 | options: {
59 | limit: 100192,
60 | },
61 | },
62 | ],
63 | },
64 | {
65 | test: /\.css$/,
66 | use: ['style-loader', 'css-loader'],
67 | },
68 | {
69 | test: /\.ttf$/,
70 | use: ['file-loader'],
71 | },
72 | {
73 | test: /\.(graphql|gql)$/,
74 | exclude: /node_modules/,
75 | loader: 'graphql-tag/loader',
76 | },
77 | ],
78 | },
79 | plugins: [
80 | new HtmlWebpackPlugin({
81 | title: 'Waypoint',
82 | template: require('html-webpack-template'),
83 | appMountId: 'root',
84 | inject: false,
85 | favicon: './icons/icon.png',
86 | }),
87 | new webpack.HotModuleReplacementPlugin(),
88 | new webpack.DefinePlugin({
89 | PRODUCTION: env.production === true,
90 | }),
91 | new webpack.optimize.LimitChunkCountPlugin({
92 | maxChunks: 1,
93 | }),
94 | ],
95 | });
96 |
--------------------------------------------------------------------------------