├── .gitignore ├── README.md ├── package.json ├── public ├── 404.html ├── CNAME ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── server ├── global.d.ts ├── publisher.tsx ├── replicator.ts └── tsconfig.json ├── src ├── App.css ├── App.test.tsx ├── App.tsx ├── ExpandingTextArea.tsx ├── PageText.tsx ├── Replicate.tsx ├── db.ts ├── export.ts ├── global.d.ts ├── index.css ├── index.tsx ├── react-app-env.d.ts ├── requestPersistentStorage.ts ├── serviceWorker.ts ├── setupTests.ts ├── textarea-op.ts ├── useHistory.ts └── worker │ ├── index.ts │ └── tsconfig.json ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | /data 26 | /dist 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > NOTE: this readme is currently _aspirational_, lots of this stuff doesn't 2 | > work yet, but I'd like it to! 3 | 4 | [[Autowiki]] is a tool for creating networked documents. Autowiki is a [local-first](https://www.inkandswitch.com/local-first.html) app: you own all the data you put into it, and your data never leaves your own machine unless you want it to. 5 | 6 | ### Try it out 7 | 8 | Visit [$WEBSITE](https://web.site) to start editing right away in your browser. To back up your data, run a [replication peer](#replicationpeer). Replication peers can optionally publish a read-only version of your wiki. 9 | 10 | ## Data Model 11 | 12 | Autowiki thinks of a wiki as a single document with many interlinked pages. All your data is stored locally, and optionally [replicated](#replicationpeer) to one or more remote sites. You can connect multiple editors to a replication peer, and edits will be synchronized automatically when they connect to the peer. Autowiki uses [automerge](https://github.com/automerge/automerge) under the hood to resolve edit conflicts automatically. 13 | 14 | ## Replication Peer 15 | 16 | Browsers are not designed for resilient storage, so Autowiki provides the ability to live-backup your data as you type. Replication peers also allow you to edit the same wiki from multiple documents, and keep all your changes synchronized. 17 | 18 | To run a replication peer, you can use the `@autowiki/replication-peer` npm package: 19 | 20 | ``` 21 | $ npx @autowiki/replication-peer 22 | Listening on 0.0.0.0:3030... 23 | Secret: 202a20cd-059c-4ef9-a7ce-3f2aecef17f8 24 | ``` 25 | 26 | To connect the Autowiki editor to the replication peer, enter its publicly-reachable address along with the secret. For instance, if your server is hosted at `my.server.net`, and the replication peer is listening on port 3030, enter `202a20cd-059c-4ef9-a7ce-3f2aecef17f8@my.server.net:3030` as the replication peer address. 27 | 28 | ### Publishing 29 | 30 | Optionally, the replication peer can also publish a read-only copy of your wiki. To publish a wiki, pass the `--publish` option to `@autowiki/replication-peer`: 31 | 32 | ``` 33 | $ npx @autowiki/replication-peer --publish 34 | ``` 35 | 36 | The replication peer will publish the read-only version on the same port it listens for changes. 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autowiki", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://www.autowiki.app", 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "automerge": "^1.0.1-preview.6", 11 | "base64-arraybuffer": "^0.2.0", 12 | "cors": "^2.8.5", 13 | "debounce": "^1.2.0", 14 | "express": "^4.17.1", 15 | "express-ws": "^4.0.0", 16 | "gh-pages": "^3.1.0", 17 | "idb": "^7.0.0", 18 | "node-localstorage": "^2.1.5", 19 | "react": "^16.12.0", 20 | "react-dom": "^16.12.0", 21 | "react-scripts": "^5.0.0", 22 | "reconnecting-websocket": "^4.4.0", 23 | "rehype-raw": "^6.1.1", 24 | "rehype-stringify": "^9.0.3", 25 | "remark": "^14.0.2", 26 | "remark-gfm": "^3.0.1", 27 | "remark-highlight.js": "^7.0.1", 28 | "remark-rehype": "^10.1.0", 29 | "remark-truncate-links": "^1.0.3", 30 | "remark-wiki-link": "^1.0.4", 31 | "typescript": "4.4.4" 32 | }, 33 | "scripts": { 34 | "predeploy": "npm run build", 35 | "deploy": "gh-pages -d build", 36 | "replicator": "ts-node server/replicator.ts", 37 | "start": "react-scripts start", 38 | "build": "react-scripts build", 39 | "test": "react-scripts test", 40 | "eject": "react-scripts eject" 41 | }, 42 | "eslintConfig": { 43 | "extends": "react-app" 44 | }, 45 | "browserslist": { 46 | "production": [ 47 | ">0.2%", 48 | "not dead", 49 | "not op_mini all" 50 | ], 51 | "development": [ 52 | "last 1 chrome version", 53 | "last 1 firefox version", 54 | "last 1 safari version" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "@types/cors": "^2.8.6", 59 | "@types/debounce": "^1.2.0", 60 | "@types/express": "^4.17.3", 61 | "@types/express-ws": "^3.0.0", 62 | "@types/jest": "^24.0.0", 63 | "@types/node": "^17.0.0", 64 | "@types/node-localstorage": "^1.3.0", 65 | "@types/react": "^16.9.0", 66 | "@types/react-dom": "^16.9.0", 67 | "@types/uuid": "^8.3.4", 68 | "@types/ws": "^8.2.2", 69 | "ts-node": "^10.5.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | www.autowiki.app 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nornagon/autowiki/9ddb19500d5a4a1c815e7c900d81746e4c1d46f0/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 37 | 38 |
39 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nornagon/autowiki/9ddb19500d5a4a1c815e7c900d81746e4c1d46f0/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nornagon/autowiki/9ddb19500d5a4a1c815e7c900d81746e4c1d46f0/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'uuid'; 2 | declare module 'basic-auth'; 3 | declare module 'y-websocket/bin/utils'; 4 | -------------------------------------------------------------------------------- /server/publisher.tsx: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | // import Automerge from 'automerge' 3 | // import ReactDOMServer from 'react-dom/server'; 4 | // import React from 'react'; 5 | // import PageText from '../src/PageText'; 6 | // import WebSocket from 'ws'; 7 | // import * as path from 'path'; 8 | // import * as fs from 'fs'; 9 | 10 | // function StaticPage({title, text}: {title: string, text: string}) { 11 | // return ( 12 | //
13 | //

{title}

14 | //
15 | //
16 | // ) 17 | // } 18 | 19 | // const app = express() 20 | // app.use(express.static(path.join(__dirname, '../../build'))) 21 | // const peer = process.argv[2] 22 | 23 | // const docSet = new Automerge.DocSet>() 24 | 25 | // const ws = new WebSocket(peer + '/_changes') 26 | // ws.on('open', () => { 27 | // const conn = new Automerge.Connection(docSet, (msg) => { 28 | // ws.send(JSON.stringify(msg)) 29 | // }) 30 | // ws.on('message', (data) => { 31 | // conn.receiveMsg(JSON.parse(data as string)) 32 | // }) 33 | // ws.on('close', (code, reason) => { 34 | // console.log('close', code, reason) 35 | // }) 36 | // }) 37 | 38 | // app.get('/*', (req, res, next) => { 39 | // const text = (docSet.getDoc('wiki')[req.params[0]] ?? '').toString() 40 | // fs.readFile(path.join(__dirname, '../../build/index.html'), (err, data) => { 41 | // if (err) return next(err) 42 | // const body = `
${ReactDOMServer.renderToStaticMarkup(StaticPage({ title: req.params[0], text }))}
` 43 | // const page = data.toString('utf8').replace(/