|
| 1 | +<template> |
| 2 | + <CoreScrollable class="meta p-4 md:p-2"> |
| 3 | + <div class="flex flex-col flex-grow"> |
| 4 | + <CoreLink v-if="hasHistory" :to="{ path: `/docs/${doc.id}/versions` }" class="sidebar-link w-full"> |
| 5 | + <HistoryIcon class="w-5" /> |
| 6 | + <span class="ml-6 md:ml-3 flex-grow text-left">History ({{ docVersions.length }})</span> |
| 7 | + </CoreLink> |
| 8 | + <div v-if="docVersion" class="flex flex-col flex-grow"> |
| 9 | + <CoreLink @click="restoreDocVersion" :to="{ path: `/docs/${doc.id}` }" class="sidebar-link w-full"> |
| 10 | + <HistoryIcon class="w-5" /> |
| 11 | + <span class="ml-6 md:ml-3 flex-grow text-left">Restore Version</span> |
| 12 | + </CoreLink> |
| 13 | + </div> |
| 14 | + <div v-else-if="doc" class="flex flex-col flex-grow"> |
| 15 | + <div> |
| 16 | + <button @click.stop="duplicateDocument" class="sidebar-link w-full"> |
| 17 | + <DuplicateIcon class="w-5" /> |
| 18 | + <span class="ml-6 md:ml-3 flex-grow text-left">Duplicate</span> |
| 19 | + </button> |
| 20 | + <DiscardableAction v-if="doc.id" :discardedAt="doc.discardedAt" :onDiscard="discardDocument" :onRestore="restoreDocument" class="sidebar-link w-full"></DiscardableAction> |
| 21 | + <button v-if="hasCodeblocks" @click="openSandbox" class="sidebar-link w-full"> |
| 22 | + <svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| 23 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" /> |
| 24 | + </svg> |
| 25 | + <span class="ml-6 md:ml-3 flex-grow text-left">Create Sandbox</span> |
| 26 | + </button> |
| 27 | + <div> |
| 28 | + <div v-if="doc.public"> |
| 29 | + <button @click="restrictDocument" class="sidebar-link w-full"> |
| 30 | + <svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| 31 | + <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" /> |
| 32 | + </svg> |
| 33 | + <span class="ml-6 md:ml-3 flex-grow text-left">Make Private</span> |
| 34 | + </button> |
| 35 | + <button @click="copyPublicUrl" class="sidebar-link w-full"> |
| 36 | + <svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| 37 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" /> |
| 38 | + </svg> |
| 39 | + <span class="ml-6 md:ml-3 flex-grow text-left">Copy Link</span> |
| 40 | + </button> |
| 41 | + <input ref="link" :value="publicUrl" type="text" class="form-text w-full mb-2" readonly data-test-public-url> |
| 42 | + </div> |
| 43 | + <div v-else class="mb-2"> |
| 44 | + <button @click="shareDocument" class="sidebar-link w-full" data-test-share-doc> |
| 45 | + <svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| 46 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z" /> |
| 47 | + </svg> |
| 48 | + <span class="ml-6 md:ml-3 flex-grow text-left">Make Public</span> |
| 49 | + </button> |
| 50 | + </div> |
| 51 | + </div> |
| 52 | + </div> |
| 53 | + <div class="mt-4"> |
| 54 | + <TagLink v-for="tag in doc.tags" :key="tag" :tag="tag" class="sidebar-link" /> |
| 55 | + </div> |
| 56 | + <div class="mt-4"> |
| 57 | + <DocLink v-for="reference in references" :key="reference.id" :doc="reference" class="sidebar-link" /> |
| 58 | + </div> |
| 59 | + <div class="mt-4"> |
| 60 | + <div v-for="task in doc.tasks" class="flex items-center px-3 py-2 my-1 md:px-2 md:py-1"> |
| 61 | + <svg height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| 62 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> |
| 63 | + </svg> |
| 64 | + <span class="flex-grow overflow-hidden truncate ml-3">{{ task }}</span> |
| 65 | + </div> |
| 66 | + </div> |
| 67 | + <div class="flex flex-col justify-end flex-grow px-3 md:p-2 mt-4 mb-3 md:mb-1"> |
| 68 | + <div v-if="doc.updatedAt"> |
| 69 | + <small class="text-gray-700">Last Saved</small> |
| 70 | + <div class="capitalize pt-2 md:pt-1">{{ savedAt }}</div> |
| 71 | + </div> |
| 72 | + <div v-if="doc.createdAt" class="mt-3 md:mt-2"> |
| 73 | + <small class="text-gray-700">Created</small> |
| 74 | + <div class="pt-2 md:pt-1">{{ createdAt }}</div> |
| 75 | + </div> |
| 76 | + <div v-if="doc.updatedAt" class="mt-3 md:mt-2"> |
| 77 | + <small class="text-gray-700">Updated</small> |
| 78 | + <div class="pt-2 md:pt-1">{{ updatedAt }}</div> |
| 79 | + </div> |
| 80 | + <div v-if="doc.discardedAt" class="mt-3 md:mt-2"> |
| 81 | + <small class="text-gray-700">Discarded</small> |
| 82 | + <div class="pt-2 md:pt-1">{{ discardedAt }}</div> |
| 83 | + </div> |
| 84 | + </div> |
| 85 | + </div> |
| 86 | + </div> |
| 87 | + </CoreScrollable> |
| 88 | +</template> |
| 89 | + |
| 90 | +<script> |
| 91 | +import { TrashIcon as DiscardIcon, DocumentDuplicateIcon as DuplicateIcon, ClockIcon as HistoryIcon, LockClosedIcon as PrivateIcon, LockOpenIcon as PublicIcon } from '@heroicons/vue/24/outline' |
| 92 | +import moment from 'moment' |
| 93 | +import { useStore } from 'vuex' |
| 94 | +import DiscardableAction from '#root/components/DiscardableAction.vue' |
| 95 | +import DocLink from '#root/components/DocLink.vue' |
| 96 | +import TagLink from '#root/components/TagLink.vue' |
| 97 | +import CodeSandbox from '#root/src/common/code_sandbox' |
| 98 | +import { parseCodeblocks, parseReferences } from '#root/src/common/parsers' |
| 99 | +import Doc from '#root/src/models/doc' |
| 100 | +
|
| 101 | +import { |
| 102 | + DISCARD_DOCUMENT, |
| 103 | + DUPLICATE_DOCUMENT, |
| 104 | + RESTORE_DOCUMENT, |
| 105 | + RESTRICT_DOCUMENT, |
| 106 | + SHARE_DOCUMENT, |
| 107 | + SET_RIGHT_SIDEBAR_VISIBILITY, |
| 108 | +} from '#root/src/store/actions' |
| 109 | +
|
| 110 | +export default { |
| 111 | + components: { |
| 112 | + DiscardIcon, |
| 113 | + DiscardableAction, |
| 114 | + DocLink, |
| 115 | + DuplicateIcon, |
| 116 | + HistoryIcon, |
| 117 | + PrivateIcon, |
| 118 | + PublicIcon, |
| 119 | + TagLink, |
| 120 | + }, |
| 121 | + setup() { |
| 122 | + const store = useStore() |
| 123 | + const { doc } = useDocs() |
| 124 | + const { docVersion, docVersions } = useDocVersions(doc) |
| 125 | + const hasHistory = computed(() => docVersions.value.length > 0) |
| 126 | +
|
| 127 | + const restoreDocVersion = () => { |
| 128 | + store.commit('EDIT_DOCUMENT', new Doc({ ...doc.value, text: docVersion.value.text })) |
| 129 | + } |
| 130 | +
|
| 131 | + return { |
| 132 | + docVersion, |
| 133 | + docVersions, |
| 134 | + hasHistory, |
| 135 | + restoreDocVersion, |
| 136 | + } |
| 137 | + }, |
| 138 | + data() { |
| 139 | + return { |
| 140 | + now: moment(), |
| 141 | + ticker: null, |
| 142 | + } |
| 143 | + }, |
| 144 | + computed: { |
| 145 | + codeblocks() { |
| 146 | + return parseCodeblocks(this.doc.text) |
| 147 | + }, |
| 148 | + createdAt() { |
| 149 | + if (this.$route.params.docId) { |
| 150 | + return moment(this.doc.createdAt).format('ddd, MMM Do, YYYY [at] h:mm A') |
| 151 | + } |
| 152 | +
|
| 153 | + return 'Not yet created' |
| 154 | + }, |
| 155 | + discardedAt() { |
| 156 | + return moment(this.doc.discardedAt).format('ddd, MMM Do, YYYY [at] h:mm A') |
| 157 | + }, |
| 158 | + doc() { |
| 159 | + return this.$store.getters.decrypted.find((doc) => doc.id === this.$route.params.docId) |
| 160 | + }, |
| 161 | + hasCodeblocks() { |
| 162 | + return this.codeblocks.length > 0 |
| 163 | + }, |
| 164 | + publicUrl() { |
| 165 | + const path = this.$router.resolve({ path: `/public/${this.doc.id}` }).href |
| 166 | +
|
| 167 | + return `${location.protocol}//${location.host}${path}` |
| 168 | + }, |
| 169 | + references() { |
| 170 | + const references = parseReferences(this.doc.text) |
| 171 | +
|
| 172 | + return this.$store.getters.kept.filter((doc) => { |
| 173 | + return references.includes(doc.id) |
| 174 | + }) |
| 175 | + }, |
| 176 | + savedAt() { |
| 177 | + if (this.$route.params.docId) { |
| 178 | + if (this.now.diff(this.doc.updatedAt, 'seconds') < 5) { |
| 179 | + return 'just now' |
| 180 | + } |
| 181 | + else { |
| 182 | + return `${moment(this.doc.updatedAt).from(this.now, true)} ago` |
| 183 | + } |
| 184 | + } |
| 185 | +
|
| 186 | + return 'Not yet saved' |
| 187 | + }, |
| 188 | + updatedAt() { |
| 189 | + if (this.$route.params.docId) { |
| 190 | + return moment(this.doc.updatedAt).format('ddd, MMM Do, YYYY [at] h:mm A') |
| 191 | + } |
| 192 | +
|
| 193 | + return 'Not yet updated' |
| 194 | + }, |
| 195 | + }, |
| 196 | + methods: { |
| 197 | + async copyPublicUrl() { |
| 198 | + // copy link to clipboard |
| 199 | + this.$refs.link.select() |
| 200 | + document.execCommand('copy') |
| 201 | + }, |
| 202 | + async discardDocument() { |
| 203 | + this.$store.dispatch(DISCARD_DOCUMENT, { id: this.doc.id }) |
| 204 | +
|
| 205 | + this.$router.push({ path: '/docs/new' }) |
| 206 | + }, |
| 207 | + async duplicateDocument() { |
| 208 | + const newDocId = await this.$store.dispatch(DUPLICATE_DOCUMENT, { id: this.doc.id }) |
| 209 | +
|
| 210 | + this.$router.push({ path: `/docs/${newDocId}` }) |
| 211 | + }, |
| 212 | + async openSandbox() { |
| 213 | + const files = this.codeblocks.reduce((agg, codeblock, index) => { |
| 214 | + const filename = codeblock.filename || [index, (codeblock.language || 'txt')].join('.') |
| 215 | +
|
| 216 | + return { |
| 217 | + ...agg, |
| 218 | + [filename]: { |
| 219 | + content: codeblock.code, |
| 220 | + }, |
| 221 | + } |
| 222 | + }, {}) |
| 223 | +
|
| 224 | + CodeSandbox.create(files).then(sandbox_id => CodeSandbox.open(sandbox_id)) |
| 225 | + }, |
| 226 | + async restoreDocument() { |
| 227 | + this.$store.dispatch(RESTORE_DOCUMENT, { id: this.doc.id }) |
| 228 | + }, |
| 229 | + async restrictDocument() { |
| 230 | + this.$store.dispatch(RESTRICT_DOCUMENT, { id: this.doc.id }) |
| 231 | + }, |
| 232 | + async shareDocument() { |
| 233 | + this.$store.dispatch(SHARE_DOCUMENT, { id: this.doc.id }) |
| 234 | + }, |
| 235 | + async toggleMeta() { |
| 236 | + this.$store.dispatch(SET_RIGHT_SIDEBAR_VISIBILITY, !this.$store.state.showRightSidebar) |
| 237 | + }, |
| 238 | + }, |
| 239 | + async beforeUnmount() { |
| 240 | + clearInterval(this.ticker) |
| 241 | + }, |
| 242 | + async mounted() { |
| 243 | + this.mounted = true |
| 244 | +
|
| 245 | + this.ticker = setInterval(() => { |
| 246 | + this.now = moment() |
| 247 | + }, 5000) |
| 248 | + }, |
| 249 | +} |
| 250 | +</script> |
0 commit comments