Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@
## Features

- [x] Dark and light themes
- [x] Hybrid markdown rendering
- [x] Hybrid plain-text Markdown rendering
- [x] Syntax highlighting for many common languages (in code blocks)
- [x] Client-side (end-to-end) encryption support
- [x] Cross-platform (desktop and mobile)
- [x] Client-side (end-to-end) encryption
- [x] Offline-first progressive web app
- [x] Tag-based document organization
- [x] Regex full-text search
- [x] Tag-based file organization
- [x] Drag-and-drop or paste to upload files
- [x] Keyboard shortcuts
- [x] Regex search
- [x] Context switching
- [x] Built with modern technologies
- [x] Modern technologies
- [x] Vim Mode support

## Questions

Expand Down
11 changes: 7 additions & 4 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<span>Redirecting you to Stripe for checkout</span>
</div>
</div>
<ChangeLog v-if="!home" />
<ChangeLog v-if="!home && !publicDoc" />
<div>
<router-view :inheritAttrs="true" class="flex-grow flex-shrink min-h-0"></router-view>
<div class="flex flex-col min-w-0 max-w-full rounded break-words bg-white notification fixed top-0 right-0 m-4 md:m-2 dark:bg-gray-800" :class="{ 'hidden': !showModal }">
Expand Down Expand Up @@ -66,6 +66,12 @@ export default {
ligatures() {
return this.$store.state.settings.editor.ligatures
},
publicDoc() {
return this.$route.name === 'public_doc'
},
showStripeModal() {
return this.$store.state.showStripeModal
},
sizes() {
if (this.mq.current === 'xs') return ['xs xs-plus']
if (this.mq.current === 'sm') return ['sm xs-plus sm-plus']
Expand All @@ -75,9 +81,6 @@ export default {

return []
},
showStripeModal() {
return this.$store.state.showStripeModal
},
theme() {
return this.$store.state.settings.theme
},
Expand Down
4 changes: 3 additions & 1 deletion src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div class="gutter h-8 flex-grow" @click="focusEditorEnd"></div>
</div>
</div>
<div v-if="!showRightSidebar && text" class="fixed top-4 right-4 z-index-10 hidden md:block">
<div v-if="!readonly && !showRightSidebar && text" class="fixed top-4 right-4 z-index-10 hidden md:block">
<button @click="toggleMeta" class="button button-size-medium button-color-gray">
<svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
Expand Down Expand Up @@ -100,7 +100,9 @@ export default defineComponent({
},
interface: {
appearance: this.appearance,
attribution: false,
images: this.settings.images.enabled,
readonly: this.readonly,
spellcheck: this.settings.spellcheck,
},
selections: this.initialSelections || [],
Expand Down
46 changes: 23 additions & 23 deletions src/components/TheRightSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<span class="ml-3">Hide</span>
</button>
</div>
<div v-if="document" class="flex flex-col flex-grow">
<div v-if="doc" class="flex flex-col flex-grow">
<div>
<DiscardableAction v-if="document.id" :discardedAt="document.discardedAt" :onDiscard="discardDocument" :onRestore="restoreDocument" class="sidebar-button w-full"></DiscardableAction>
<DiscardableAction v-if="doc.id" :discardedAt="doc.discardedAt" :onDiscard="discardDocument" :onRestore="restoreDocument" class="sidebar-button w-full"></DiscardableAction>
<button @click.stop="duplicateDocument" class="sidebar-button w-full">
<svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7v8a2 2 0 002 2h6M8 7V5a2 2 0 012-2h4.586a1 1 0 01.707.293l4.414 4.414a1 1 0 01.293.707V15a2 2 0 01-2 2h-2M8 7H6a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2v-2" />
Expand All @@ -25,7 +25,7 @@
<span class="ml-6 md:ml-3 flex-grow text-left">Create Sandbox</span>
</button>
<div>
<div v-if="document.public">
<div v-if="doc.public">
<button @click="restrictDocument" class="sidebar-button w-full">
<svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
Expand All @@ -51,30 +51,30 @@
</div>
</div>
<div class="mt-4">
<TagLink v-for="tag in document.tags" :key="tag" :tag="tag" class="sidebar-link"></TagLink>
<TagLink v-for="tag in doc.tags" :key="tag" :tag="tag" class="sidebar-link"></TagLink>
</div>
<div class="mt-4">
<div v-for="task in document.tasks" class="flex items-center px-3 py-2 my-1 md:px-2 md:py-1">
<div v-for="task in doc.tasks" class="flex items-center px-3 py-2 my-1 md:px-2 md:py-1">
<svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
<span class="flex-grow overflow-hidden truncate ml-3">{{ task }}</span>
</div>
</div>
<div class="flex flex-col justify-end flex-grow px-3 md:p-2 mt-4 mb-3 md:mb-1">
<div v-if="document.updatedAt">
<div v-if="doc.updatedAt">
<small class="text-gray-700">Last Saved</small>
<div class="capitalize pt-2 md:pt-1">{{ savedAt }}</div>
</div>
<div v-if="document.createdAt" class="mt-3 md:mt-2">
<div v-if="doc.createdAt" class="mt-3 md:mt-2">
<small class="text-gray-700">Created</small>
<div class="pt-2 md:pt-1">{{ createdAt }}</div>
</div>
<div v-if="document.updatedAt" class="mt-3 md:mt-2">
<div v-if="doc.updatedAt" class="mt-3 md:mt-2">
<small class="text-gray-700">Updated</small>
<div class="pt-2 md:pt-1">{{ updatedAt }}</div>
</div>
<div v-if="document.discardedAt" class="mt-3 md:mt-2">
<div v-if="doc.discardedAt" class="mt-3 md:mt-2">
<small class="text-gray-700">Discarded</small>
<div class="pt-2 md:pt-1">{{ discardedAt }}</div>
</div>
Expand Down Expand Up @@ -117,44 +117,44 @@ export default {
},
computed: {
codeblocks() {
return parseCodeblocks(this.document.text)
return parseCodeblocks(this.doc.text)
},
createdAt() {
if (this.$route.params.id) {
return moment(this.document.createdAt).format('ddd, MMM Do, YYYY [at] h:mm A')
return moment(this.doc.createdAt).format('ddd, MMM Do, YYYY [at] h:mm A')
}

return 'Not yet created'
},
discardedAt() {
return moment(this.document.discardedAt).format('ddd, MMM Do, YYYY [at] h:mm A')
return moment(this.doc.discardedAt).format('ddd, MMM Do, YYYY [at] h:mm A')
},
document() {
doc() {
return this.$store.getters.currentDoc
},
hasCodeblocks() {
return this.codeblocks.length > 0
},
publicUrl() {
const path = this.$router.resolve({ name: 'shared', params: { id: this.document.id } }).href
const path = this.$router.resolve({ name: 'public_doc', params: { id: this.doc.id } }).href

return `${location.protocol}//${location.host}${path}`
},
savedAt() {
if (this.$route.params.id) {
if (this.now.diff(this.document.updatedAt, 'seconds') < 5) {
if (this.now.diff(this.doc.updatedAt, 'seconds') < 5) {
return 'just now'
}
else {
return `${moment(this.document.updatedAt).from(this.now, true)} ago`
return `${moment(this.doc.updatedAt).from(this.now, true)} ago`
}
}

return 'Not yet saved'
},
updatedAt() {
if (this.$route.params.id) {
return moment(this.document.updatedAt).format('ddd, MMM Do, YYYY [at] h:mm A')
return moment(this.doc.updatedAt).format('ddd, MMM Do, YYYY [at] h:mm A')
}

return 'Not yet updated'
Expand All @@ -164,15 +164,15 @@ export default {
async copyPublicUrl() {
// copy link to clipboard
this.$refs.link.select()
document.execCommand('copy')
doc.execCommand('copy')
},
async discardDocument() {
this.$store.dispatch(DISCARD_DOCUMENT, { id: this.document.id })
this.$store.dispatch(DISCARD_DOCUMENT, { id: this.doc.id })

open({ name: 'dashboard' })
},
async duplicateDocument() {
const newDocId = await this.$store.dispatch(DUPLICATE_DOCUMENT, { id: this.document.id })
const newDocId = await this.$store.dispatch(DUPLICATE_DOCUMENT, { id: this.doc.id })

open({ name: 'doc', params: { id: newDocId } })
},
Expand All @@ -191,13 +191,13 @@ export default {
CodeSandbox.create(files).then(sandbox_id => CodeSandbox.open(sandbox_id))
},
async restoreDocument() {
this.$store.dispatch(RESTORE_DOCUMENT, { id: this.document.id })
this.$store.dispatch(RESTORE_DOCUMENT, { id: this.doc.id })
},
async restrictDocument() {
this.$store.dispatch(RESTRICT_DOCUMENT, { id: this.document.id })
this.$store.dispatch(RESTRICT_DOCUMENT, { id: this.doc.id })
},
async shareDocument() {
this.$store.dispatch(SHARE_DOCUMENT, { id: this.document.id })
this.$store.dispatch(SHARE_DOCUMENT, { id: this.doc.id })
},
async toggleMeta() {
this.$store.dispatch(SET_RIGHT_SIDEBAR_VISIBILITY, !this.$store.state.showRightSidebar)
Expand Down
12 changes: 7 additions & 5 deletions src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ export const router = createRouter({
next()
},
},
{
path: 'public/:id',
name: 'public_doc',
component: () => import('/src/views/Editor.vue'),
props: { readonly: true },
},
{
path: 'force-graph',
name: 'force_graph',
Expand Down Expand Up @@ -237,11 +243,7 @@ export const router = createRouter({
{
path: 'shared/:id',
name: 'shared',
component: () => import('/src/views/Editor.vue'),
props: {
default: true,
readonly: true,
},
redirect: { name: 'public_doc' },
},
// context switcher
{
Expand Down
30 changes: 17 additions & 13 deletions src/views/Editor.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<Editor ref="editable" :appearance="appearance" :initialSelections="initialSelections" :settings="settings" :text="doc.text" @input="input" />
<Editor ref="editable" :appearance="appearance" :initialSelections="initialSelections" :readonly="readonly" :settings="settings" :text="doc.text" @input="input" />
</template>

<script>
Expand Down Expand Up @@ -105,20 +105,24 @@ export default {
return unpack(packed, { privateKey: this.$store.state.settings.crypto.privateKey })
},
async input(text) {
if (this.id) {
this.$store.dispatch(EDIT_DOCUMENT, { id: this.doc.id, text })
} else {
this.$store.dispatch(ADD_DOCUMENT, new Doc({ id: this.doc.id, text }))
if (!this.readonly) {
// ReadOnly mode means we are viewing a shared doc.
// Todo: Create a new view for shared docs, and store shared docs in a new collection.
if (this.id) {
this.$store.dispatch(EDIT_DOCUMENT, { id: this.doc.id, text })
} else {
this.$store.dispatch(ADD_DOCUMENT, new Doc({ id: this.doc.id, text }))

open({
name: 'doc',
params: {
id: this.doc.id,
props: {
initialSelections: this.$refs.editable.getSelections(),
open({
name: 'doc',
params: {
id: this.doc.id,
props: {
initialSelections: this.$refs.editable.getSelections(),
},
},
},
})
})
}
}
},
},
Expand Down
42 changes: 21 additions & 21 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1140,9 +1140,9 @@
"@codemirror/stream-parser" "^0.19.0"

"@codemirror/language@^0.19.0":
version "0.19.9"
resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-0.19.9.tgz#9025b88933a05ebb27f16db6f9297b7c069109f0"
integrity sha512-Rqh7sJduwNVWoLXbOx1nf1vh0zXSIMJ3XDSyISuiIDaOo6Ps7aXgO5BCn+oa3/1RIroEu6vo4PP/zP6B7NSRWg==
version "0.19.10"
resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-0.19.10.tgz#c3d1330fa5de778c6b6b5177af5572a3d9d596b5"
integrity sha512-yA0DZ3RYn2CqAAGW62VrU8c4YxscMQn45y/I9sjBlqB1e2OTQLg4CCkMBuMSLXk4xaqjlsgazeOQWaJQOKfV8Q==
dependencies:
"@codemirror/state" "^0.19.0"
"@codemirror/text" "^0.19.0"
Expand Down Expand Up @@ -1634,9 +1634,9 @@
integrity sha512-9FqhNjKQWpQ3fGnSOCovHOm+yhhiorKEqYLAfd525jWavunDJcx8rOW6i6ozAh+FbwcYMkL7b+3j4UR/30MpoQ==

"@grpc/grpc-js@^1.3.2":
version "1.5.10"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.5.10.tgz#12f0b33b338fad5a0e48ee1d5999b5b08da60bcc"
integrity sha512-++oAubX/7rJzlqH0ShyzDENNNDHYrlttdc3NM40KlaVQDcgGqQknuPoavmyTC+oNUDyxPCX5dHceKhfcgN3tiw==
version "1.6.0"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.0.tgz#b9616121fdff6b2fa21011be96e35f03e57a5c78"
integrity sha512-KwNibKGx1qmAwsrYu75FhUo3+m6GMJoBfdnYZte9YQ2EM3hZ5Ez+8+Q+FAMONtfU0XJGUkGK5S+q4CXSjx5Ahw==
dependencies:
"@grpc/proto-loader" "^0.6.4"
"@types/node" ">=12.12.47"
Expand Down Expand Up @@ -2105,9 +2105,9 @@
integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==

"@writewithocto/ink@^0.6.2":
version "0.6.8"
resolved "https://registry.yarnpkg.com/@writewithocto/ink/-/ink-0.6.8.tgz#5247154817f11576c119411da12669394ffd73e6"
integrity sha512-vb98qwEPQenn5wkuwekqxhjj11tHVHcEH+DELJy3pYgrRhHj283OxkFhqeHOLeT0Jpfjka+bBfoeHVpdKlkrBQ==
version "0.6.9"
resolved "https://registry.yarnpkg.com/@writewithocto/ink/-/ink-0.6.9.tgz#3d6e6b2d9237c345de87723def4dfa9a2f80b890"
integrity sha512-t8Dww4BHHf1kc4fC2pvG6UvYwC4beFEYXEYlQ3q67/TAccp6H1PbEmlO2+3Dmyw66uBa7dsk2QLmPKWiRAsbqg==
dependencies:
"@codemirror/commands" "^0.19.0"
"@codemirror/comment" "^0.19.0"
Expand All @@ -2127,9 +2127,9 @@
is-plain-object "^5.0.0"

"@writewithocto/vue-ink@^0.6.0":
version "0.6.4"
resolved "https://registry.yarnpkg.com/@writewithocto/vue-ink/-/vue-ink-0.6.4.tgz#15dd6d2d06503b8d025121dcea60964dd9777aeb"
integrity sha512-d7LIw5eS8gubkjYr0huags1+vQHlqUTI8RTlGmoIeX8Zs+dARsZnaxgUvG4meDTk3BgXZNn3rNPSZC2NUdF63g==
version "0.6.5"
resolved "https://registry.yarnpkg.com/@writewithocto/vue-ink/-/vue-ink-0.6.5.tgz#6449741355fce455dc98657f5f5f2eb915e35801"
integrity sha512-QLFR8ispzH1xRY8cGHQHiGdlpBgyhhWpuu6st36qZ1LxFaY9w+kRZNaX1c6ClDkdfokyFxc8Y1TYMgIXcWwxig==
dependencies:
"@writewithocto/ink" "^0.6.2"
vue "^3.2.31"
Expand Down Expand Up @@ -2558,9 +2558,9 @@ can-use-dom@^0.1.0:
integrity sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo=

caniuse-lite@^1.0.30001317:
version "1.0.30001322"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001322.tgz#2e4c09d11e1e8f852767dab287069a8d0c29d623"
integrity sha512-neRmrmIrCGuMnxGSoh+x7zYtQFFgnSY2jaomjU56sCkTA6JINqQrxutF459JpWcWRajvoyn95sOXq4Pqrnyjew==
version "1.0.30001323"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001323.tgz#a451ff80dec7033016843f532efda18f02eec011"
integrity sha512-e4BF2RlCVELKx8+RmklSEIVub1TWrmdhvA5kEUueummz1XyySW0DVk+3x9HyhU9MuWTa2BhqLgEuEmUwASAdCA==

canvas-color-tracker@1:
version "1.1.5"
Expand Down Expand Up @@ -2633,9 +2633,9 @@ chownr@^2.0.0:
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==

clean-css@^5.2.2:
version "5.2.4"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4"
integrity sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg==
version "5.3.0"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.0.tgz#ad3d8238d5f3549e83d5f87205189494bc7cbb59"
integrity sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ==
dependencies:
source-map "~0.6.0"

Expand Down Expand Up @@ -6103,9 +6103,9 @@ vite-plugin-pwa@^0.11.2:
workbox-window "^6.4.2"

vite@^2.5.10:
version "2.9.0"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.0.tgz#4417ce84a09d93c9e5a796b296285bc2f75d6c41"
integrity sha512-5NAnNqzPmZzJvrswZGeTS2JHrBGIzIWJA2hBTTMYuoBVEMh0xwE0b5yyIXFxf7F07hrK4ugX2LJ7q6t7iIbd4Q==
version "2.9.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.1.tgz#84bce95fae210a7beb566a0af06246748066b48f"
integrity sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ==
dependencies:
esbuild "^0.14.27"
postcss "^8.4.12"
Expand Down