update note display
This commit is contained in:
32
package-lock.json
generated
32
package-lock.json
generated
@@ -15,9 +15,9 @@
|
|||||||
"@vueuse/core": "^10.1.0",
|
"@vueuse/core": "^10.1.0",
|
||||||
"bootstrap-icons": "^1.10.5",
|
"bootstrap-icons": "^1.10.5",
|
||||||
"daisyui": "^2.51.6",
|
"daisyui": "^2.51.6",
|
||||||
|
"date-fns": "^2.29.3",
|
||||||
"dompurify": "^3.0.2",
|
"dompurify": "^3.0.2",
|
||||||
"firebase": "^9.20.0",
|
"firebase": "^9.20.0",
|
||||||
"font-awesome": "^4.7.0",
|
|
||||||
"hamburgers": "^1.2.1",
|
"hamburgers": "^1.2.1",
|
||||||
"marked": "^4.3.0",
|
"marked": "^4.3.0",
|
||||||
"shortid": "^2.2.16",
|
"shortid": "^2.2.16",
|
||||||
@@ -1760,6 +1760,18 @@
|
|||||||
"postcss": "^8.1.6"
|
"postcss": "^8.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "2.29.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||||
|
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.11"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/date-fns"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/de-indent": {
|
"node_modules/de-indent": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||||
@@ -1924,14 +1936,6 @@
|
|||||||
"@firebase/util": "1.9.3"
|
"@firebase/util": "1.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/font-awesome": {
|
|
||||||
"version": "4.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
|
||||||
"integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||||
@@ -4416,6 +4420,11 @@
|
|||||||
"tailwindcss": "^3"
|
"tailwindcss": "^3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"version": "2.29.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||||
|
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
|
||||||
|
},
|
||||||
"de-indent": {
|
"de-indent": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||||
@@ -4561,11 +4570,6 @@
|
|||||||
"@firebase/util": "1.9.3"
|
"@firebase/util": "1.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"font-awesome": {
|
|
||||||
"version": "4.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
|
||||||
"integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg=="
|
|
||||||
},
|
|
||||||
"fraction.js": {
|
"fraction.js": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
"@vueuse/core": "^10.1.0",
|
"@vueuse/core": "^10.1.0",
|
||||||
"bootstrap-icons": "^1.10.5",
|
"bootstrap-icons": "^1.10.5",
|
||||||
"daisyui": "^2.51.6",
|
"daisyui": "^2.51.6",
|
||||||
|
"date-fns": "^2.29.3",
|
||||||
"dompurify": "^3.0.2",
|
"dompurify": "^3.0.2",
|
||||||
"firebase": "^9.20.0",
|
"firebase": "^9.20.0",
|
||||||
"font-awesome": "^4.7.0",
|
|
||||||
"hamburgers": "^1.2.1",
|
"hamburgers": "^1.2.1",
|
||||||
"marked": "^4.3.0",
|
"marked": "^4.3.0",
|
||||||
"shortid": "^2.2.16",
|
"shortid": "^2.2.16",
|
||||||
|
|||||||
16
src/App.vue
16
src/App.vue
@@ -3,14 +3,14 @@ import { ref } from 'vue'
|
|||||||
import TopBar from '@/components/TopBar.vue'
|
import TopBar from '@/components/TopBar.vue'
|
||||||
import SideBar from '@/components/SideBar.vue'
|
import SideBar from '@/components/SideBar.vue'
|
||||||
import Note from '@/components/Note.vue'
|
import Note from '@/components/Note.vue'
|
||||||
import { activeNote } from '@/composables/useNotes'
|
import { activeNote, updateNote } from '@/composables/useNotes'
|
||||||
|
|
||||||
const sideBarCollapsed = ref(false)
|
const sideBarCollapsed = ref(false)
|
||||||
|
|
||||||
const viewModes: ViewMode[] = [
|
const viewModes: ViewMode[] = [
|
||||||
{ name: 'Note', icon: 'card-text' },
|
{ name: 'Note', icon: 'fas fa-sticky-note fa-fw' },
|
||||||
{ name: 'List', icon: 'list-task' },
|
{ name: 'List', icon: 'fas fa-list fa-fw' },
|
||||||
{ name: 'Mindmap', icon: 'diagram-3' },
|
{ name: 'Mindmap', icon: 'fas fa-project-diagram fa-fw' },
|
||||||
]
|
]
|
||||||
const activeViewMode = ref(viewModes[0])
|
const activeViewMode = ref(viewModes[0])
|
||||||
</script>
|
</script>
|
||||||
@@ -31,7 +31,13 @@ const activeViewMode = ref(viewModes[0])
|
|||||||
class="transition[margin-left] absolute bottom-0 left-0 right-0 top-[50px] flex overflow-auto border-x-[1px] bg-white px-10 py-6 duration-200 ease-out"
|
class="transition[margin-left] absolute bottom-0 left-0 right-0 top-[50px] flex overflow-auto border-x-[1px] bg-white px-10 py-6 duration-200 ease-out"
|
||||||
:class="sideBarCollapsed ? 'ml-0' : 'ml-sidebar'"
|
:class="sideBarCollapsed ? 'ml-0' : 'ml-sidebar'"
|
||||||
>
|
>
|
||||||
<Note v-if="activeNote" :note="activeNote" class="w-full" />
|
<Note
|
||||||
|
v-if="activeNote"
|
||||||
|
:key="activeNote.id"
|
||||||
|
:note="activeNote"
|
||||||
|
class="w-full"
|
||||||
|
@update="(note) => updateNote(note.id, note)"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,17 +1,69 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
import { ref, watch, computed } from 'vue'
|
||||||
|
import { formatDate } from '@/utils/helpers'
|
||||||
|
import { notes, activeNote } from '@/composables/useNotes'
|
||||||
|
import { notesRelations } from '@/composables/useNotes'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
note: Note
|
note: Note
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update', note: Note): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const noteTitle = ref(props.note.title)
|
||||||
|
watch(noteTitle, () => {
|
||||||
|
const updatedNote: Note = {
|
||||||
|
...props.note,
|
||||||
|
title: noteTitle.value,
|
||||||
|
}
|
||||||
|
emit('update', updatedNote)
|
||||||
|
})
|
||||||
|
|
||||||
|
const references = computed<Note[]>(() => {
|
||||||
|
return notesRelations.value[props.note.id].from
|
||||||
|
.map((noteId) => {
|
||||||
|
return notes.value.find((note) => note.id === noteId)
|
||||||
|
})
|
||||||
|
.filter((note): note is Note => note !== undefined)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="flex flex-col">
|
||||||
<h1 class="flex items-center pb-2 text-3xl">
|
<h1 class="mb-2 flex items-center rounded-md text-3xl hover:bg-gray-200">
|
||||||
<i
|
<i
|
||||||
class="bi bi-house mr-2 text-base text-secondary"
|
class="bi bi-house mr-2 text-base text-secondary"
|
||||||
v-if="note.isRoot"
|
v-if="props.note.isRoot"
|
||||||
></i
|
></i
|
||||||
>{{ note.title }}
|
><input
|
||||||
|
type="text"
|
||||||
|
class="flex-1 bg-transparent py-1 outline-none"
|
||||||
|
v-model="noteTitle"
|
||||||
|
/>
|
||||||
</h1>
|
</h1>
|
||||||
<div>{{ note.content }}</div>
|
<div class="flex-1">{{ note.content }}</div>
|
||||||
|
<div class="card mt-3 border-[1px]" v-if="references.length > 0">
|
||||||
|
<div class="card-body px-3 py-3">
|
||||||
|
<ul class="menu rounded-md">
|
||||||
|
<li class="menu-title !opacity-100">
|
||||||
|
<span class="card-title text-secondary"
|
||||||
|
>References
|
||||||
|
<div class="badge-outline badge">{{ references.length }}</div>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li v-for="reference in references">
|
||||||
|
<a class="rounded-md" @click="activeNote = reference"
|
||||||
|
><i class="far fa-file-alt fa-fw" />{{ reference.title }}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-3" />
|
||||||
|
<div class="flex text-sm text-secondary">
|
||||||
|
<span>{{ note.wordCount }} words</span>
|
||||||
|
<span class="ml-auto">Last modified {{ formatDate(note.modified) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, computed } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import { notes, findNotes, activeNote } from '@/composables/useNotes'
|
import { notes, findNotes, activeNote } from '@/composables/useNotes'
|
||||||
|
import { formatDate } from '@/utils/helpers'
|
||||||
|
|
||||||
const active = ref(false)
|
const active = ref(false)
|
||||||
watch(active, () => {
|
watch(active, () => {
|
||||||
@@ -12,10 +13,12 @@ const results = computed<Note[]>(() => {
|
|||||||
return query.value ? findNotes(query.value) : notes.value
|
return query.value ? findNotes(query.value) : notes.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const click = (note: Note) => {
|
const goToNote = (note: Note) => {
|
||||||
activeNote.value = note
|
activeNote.value = note
|
||||||
active.value = false
|
active.value = false
|
||||||
|
if (queryElem.value) queryElem.value.blur()
|
||||||
}
|
}
|
||||||
|
const queryElem = ref<HTMLInputElement | null>(null)
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div id="search-container" class="relative h-full flex-1">
|
<div id="search-container" class="relative h-full flex-1">
|
||||||
@@ -27,6 +30,7 @@ const click = (note: Note) => {
|
|||||||
@mousedown="active = true"
|
@mousedown="active = true"
|
||||||
@blur="active = false"
|
@blur="active = false"
|
||||||
v-model="query"
|
v-model="query"
|
||||||
|
ref="queryElem"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="z-1000 dropdown absolute left-0 right-0 top-[100%]"
|
class="z-1000 dropdown absolute left-0 right-0 top-[100%]"
|
||||||
@@ -40,11 +44,10 @@ const click = (note: Note) => {
|
|||||||
v-for="note in results"
|
v-for="note in results"
|
||||||
v-if="results.length > 0"
|
v-if="results.length > 0"
|
||||||
class="flex flex-row"
|
class="flex flex-row"
|
||||||
:class="{ disabled: activeNote?.id === note.id }"
|
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="flex-1 items-center px-2 py-1"
|
class="flex-1 items-center px-2 py-1"
|
||||||
@click.stop.prevent="() => click(note)"
|
@click.stop.prevent="() => goToNote(note)"
|
||||||
@mousedown.prevent
|
@mousedown.prevent
|
||||||
:class="{ disabled: activeNote?.id === note.id }"
|
:class="{ disabled: activeNote?.id === note.id }"
|
||||||
>
|
>
|
||||||
@@ -53,7 +56,8 @@ const click = (note: Note) => {
|
|||||||
v-if="activeNote?.id === note.id"
|
v-if="activeNote?.id === note.id"
|
||||||
>current</span
|
>current</span
|
||||||
>
|
>
|
||||||
{{ note.title }}</a
|
<span class="flex-1">{{ note.title }}</span>
|
||||||
|
<span>{{ formatDate(note.modified) }}</span></a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li v-else><a>No notes found</a></li>
|
<li v-else><a>No notes found</a></li>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { rootNote } from '@/composables/useNotes'
|
import { rootNote, notes, activeNote } from '@/composables/useNotes'
|
||||||
import SideBarMenu from '@/components/SideBar/SideBarMenu.vue'
|
import SideBarMenu from '@/components/SideBar/SideBarMenu.vue'
|
||||||
import SideBarMenuItem from '@/components/SideBar/SideBarMenuItem.vue'
|
import SideBarMenuItem from '@/components/SideBar/SideBarMenuItem.vue'
|
||||||
|
|
||||||
@@ -15,12 +15,16 @@ const emit = defineEmits<{
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="sidebar"
|
id="sidebar"
|
||||||
class="relative flex w-sidebar flex-col gap-4 overflow-y-auto px-2 py-3"
|
class="relative flex w-sidebar flex-col gap-4 overflow-y-auto px-2 py-3 text-[90%]"
|
||||||
>
|
>
|
||||||
<SideBarMenu>
|
<SideBarMenu>
|
||||||
<template #header>Root note</template>
|
<template #header>Root note</template>
|
||||||
<template #items>
|
<template #items>
|
||||||
<SideBarMenuItem icon="house">{{ rootNote?.title }}</SideBarMenuItem>
|
<SideBarMenuItem
|
||||||
|
icon="fas fa-fw fa-home"
|
||||||
|
@click="activeNote = rootNote"
|
||||||
|
>{{ rootNote?.title }}</SideBarMenuItem
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</SideBarMenu>
|
</SideBarMenu>
|
||||||
<SideBarMenu>
|
<SideBarMenu>
|
||||||
@@ -36,9 +40,16 @@ const emit = defineEmits<{
|
|||||||
</template>
|
</template>
|
||||||
</SideBarMenu>
|
</SideBarMenu>
|
||||||
<SideBarMenu>
|
<SideBarMenu>
|
||||||
<template #header>Recent notes</template>
|
<template #header
|
||||||
|
><i class="far fa-clock fa-fw mr-2" />Recent notes</template
|
||||||
|
>
|
||||||
<template #items>
|
<template #items>
|
||||||
<SideBarMenuItem>{{ rootNote?.title }}</SideBarMenuItem>
|
<SideBarMenuItem
|
||||||
|
v-for="note in notes"
|
||||||
|
icon="far fa-file-alt fa-fw"
|
||||||
|
@click="activeNote = note"
|
||||||
|
>{{ note.title }}</SideBarMenuItem
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
</SideBarMenu>
|
</SideBarMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ const props = defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<a
|
<a
|
||||||
class="mt-1 block w-full cursor-pointer rounded hover:bg-gray-200"
|
class="mt-1 block w-full cursor-pointer rounded hover:bg-gray-200 active:bg-primary active:text-primary-content"
|
||||||
:class="props.active ? 'font-bold text-primary' : 'text-secondary'"
|
:class="props.active ? 'font-bold text-primary' : 'text-secondary'"
|
||||||
>
|
>
|
||||||
<i :class="`bi bi-${props.icon}`" class="mr-2" v-if="props.icon"></i
|
<i :class="props.icon" class="mr-2" v-if="props.icon"></i><slot></slot>
|
||||||
><slot></slot>
|
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import Hamburger from '@/components/Hamburger.vue'
|
import Hamburger from '@/components/Hamburger.vue'
|
||||||
import SearchBar from '@/components/SearchBar.vue'
|
import SearchBar from '@/components/SearchBar.vue'
|
||||||
import Logo from './Logo.vue'
|
import Logo from './Logo.vue'
|
||||||
|
import { addNote } from '@/composables/useNotes'
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
sideBarCollapsed: boolean
|
sideBarCollapsed: boolean
|
||||||
}>()
|
}>()
|
||||||
@@ -13,21 +14,26 @@ const emit = defineEmits<{
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute left-0 right-0 top-0 z-[500] flex h-[50px] bg-primary">
|
<div class="absolute left-0 right-0 top-0 z-[500] flex h-[50px] bg-primary">
|
||||||
<div
|
<div
|
||||||
class="w-in mx-auto flex w-full max-w-app items-center gap-3 text-white"
|
class="w-in mx-auto flex w-full max-w-app items-center py-2.5 text-white"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex w-sidebar items-center pl-3"
|
class="flex pl-3"
|
||||||
:class="sideBarCollapsed && 'w-fit'"
|
:class="sideBarCollapsed ? 'w-fit' : 'w-sidebar pr-3'"
|
||||||
>
|
>
|
||||||
<Hamburger
|
<Hamburger
|
||||||
:side-bar-collapsed="props.sideBarCollapsed"
|
:side-bar-collapsed="props.sideBarCollapsed"
|
||||||
@toggle-side-bar="emit('toggleSideBar')"
|
@toggle-side-bar="emit('toggleSideBar')"
|
||||||
/>
|
/>
|
||||||
<Logo class="ml-auto px-3 text-2xl" />
|
<Logo class="ml-auto pl-5 text-2xl" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mr-3 flex h-full flex-1 items-center gap-2 py-2.5">
|
<div class="flex h-full flex-1 flex-row items-center gap-2 pl-5 pr-3">
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<button class="btn-outline btn-sm btn h-full text-white">+</button>
|
<button
|
||||||
|
class="btn-outline btn-sm btn h-full text-white"
|
||||||
|
@click="addNote('Untitled new note', '', true)"
|
||||||
|
>
|
||||||
|
<i class="fas fa-plus-circle text-[1.1rem]" />
|
||||||
|
</button>
|
||||||
<button class="btn-outline btn-sm btn h-full text-white">
|
<button class="btn-outline btn-sm btn h-full text-white">
|
||||||
Sign in
|
Sign in
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
import { useTitle } from '@vueuse/core'
|
import { useTitle } from '@vueuse/core'
|
||||||
import { mdToHtml } from '@/utils/markdown'
|
import { mdToHtml } from '@/utils/markdown'
|
||||||
|
import { getAllMatches } from '@/utils/helpers'
|
||||||
|
import shortid from 'shortid'
|
||||||
|
|
||||||
|
const contextedPrefix = 'c@'
|
||||||
|
|
||||||
|
const baseNotes = reactive<{ [noteId: string]: BaseNote }>({})
|
||||||
|
|
||||||
const baseNotes = ref<BaseNote[]>([])
|
|
||||||
export const notes = computed<Note[]>(() => {
|
export const notes = computed<Note[]>(() => {
|
||||||
// extract links and add word count
|
return Object.entries(baseNotes)
|
||||||
return baseNotes.value as Note[]
|
.map(([_, note]) => ({
|
||||||
|
...note,
|
||||||
|
wordCount: note.content.split(' ').length,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.modified - a.modified) as Note[]
|
||||||
|
})
|
||||||
|
watch(notes, () => {
|
||||||
|
if (notes.value.length > 0 && !activeNote.value)
|
||||||
|
activeNote.value = rootNote.value
|
||||||
})
|
})
|
||||||
|
|
||||||
export const activeNote = ref<Note>()
|
export const activeNote = ref<Note>()
|
||||||
@@ -16,16 +29,11 @@ watch(activeNote, () => {
|
|||||||
export const rootNote = computed<Note | undefined>(() =>
|
export const rootNote = computed<Note | undefined>(() =>
|
||||||
notes.value.find((note: Note) => note.isRoot)
|
notes.value.find((note: Note) => note.isRoot)
|
||||||
)
|
)
|
||||||
watch(
|
|
||||||
rootNote,
|
|
||||||
() => {
|
|
||||||
if (rootNote.value) activeNote.value = rootNote.value
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
export const setDefaultNotes = (defaultNotes: BaseNote[]) => {
|
export const setDefaultNotes = (defaultNotes: BaseNote[]) => {
|
||||||
baseNotes.value = defaultNotes
|
defaultNotes.forEach((defaultNote) => {
|
||||||
|
baseNotes[defaultNote.id] = defaultNote
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getNoteById = (noteId: string) => {
|
export const getNoteById = (noteId: string) => {
|
||||||
@@ -35,7 +43,7 @@ export const getNoteById = (noteId: string) => {
|
|||||||
export const findNotes = (query: string): Note[] => {
|
export const findNotes = (query: string): Note[] => {
|
||||||
const removeMdFromText = (mdText: string): string => {
|
const removeMdFromText = (mdText: string): string => {
|
||||||
const div = document.createElement('div')
|
const div = document.createElement('div')
|
||||||
div.innerHTML = mdToHtml(mdText, 'c@', getNoteById)
|
div.innerHTML = mdToHtml(mdText, contextedPrefix, getNoteById)
|
||||||
const textWithoutMd = div.textContent || div.innerText || ''
|
const textWithoutMd = div.textContent || div.innerText || ''
|
||||||
return textWithoutMd
|
return textWithoutMd
|
||||||
}
|
}
|
||||||
@@ -47,3 +55,80 @@ export const findNotes = (query: string): Note[] => {
|
|||||||
return matchTitle || matchContent
|
return matchTitle || matchContent
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const updateNote = (noteId: string, note: Note) => {
|
||||||
|
const updatedNote: Note = {
|
||||||
|
...note,
|
||||||
|
modified: new Date().getTime(),
|
||||||
|
}
|
||||||
|
baseNotes[noteId] = updatedNote
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addNote = (
|
||||||
|
title: string,
|
||||||
|
content: string,
|
||||||
|
goToNote: boolean = false
|
||||||
|
) => {
|
||||||
|
const id = shortid.generate()
|
||||||
|
const newNote: BaseNote = {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
created: new Date().getTime(),
|
||||||
|
modified: new Date().getTime(),
|
||||||
|
}
|
||||||
|
baseNotes[id] = newNote
|
||||||
|
if (goToNote)
|
||||||
|
activeNote.value = notes.value.find((note) => note.id === newNote.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNoteLinksByNoteId = (noteId: string): string[] => {
|
||||||
|
const note = baseNotes[noteId]
|
||||||
|
const regex = /\[\[(.*?)\]\]/g
|
||||||
|
const links = getAllMatches(regex, note.content || '')
|
||||||
|
.map((to) => notes.value.find((note) => note.title === to[1])?.id || '')
|
||||||
|
.filter((noteId) => Object.keys(baseNotes).includes(noteId))
|
||||||
|
return [...links]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NoteRelations {
|
||||||
|
id: string
|
||||||
|
to: string[]
|
||||||
|
from: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotesRelations {
|
||||||
|
[noteId: string]: {
|
||||||
|
to: string[]
|
||||||
|
from: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const notesRelations = computed(() => {
|
||||||
|
const noteIds = Object.keys(baseNotes)
|
||||||
|
const relations = noteIds
|
||||||
|
.filter((id) => id !== undefined)
|
||||||
|
.map((id) => {
|
||||||
|
const to = getNoteLinksByNoteId(id)
|
||||||
|
return { id, to }
|
||||||
|
})
|
||||||
|
.map((noteRelations, _, notesRelations): NoteRelations => {
|
||||||
|
const from = [...notesRelations]
|
||||||
|
.map((noteRelation) =>
|
||||||
|
noteRelation.to
|
||||||
|
.filter((toId) => toId === noteRelations.id)
|
||||||
|
.map(() => noteRelation.id)
|
||||||
|
)
|
||||||
|
.reduce((arr, elem) => arr.concat(elem), [])
|
||||||
|
.filter((value, index, self) => self.indexOf(value) === index)
|
||||||
|
return {
|
||||||
|
id: noteRelations.id,
|
||||||
|
to: noteRelations.to,
|
||||||
|
from,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.reduce((notes, { id, to, from }) => {
|
||||||
|
notes[id] = { to, from }
|
||||||
|
return notes
|
||||||
|
}, {} as NotesRelations)
|
||||||
|
return relations
|
||||||
|
})
|
||||||
|
|||||||
4
src/global.d.ts
vendored
4
src/global.d.ts
vendored
@@ -10,10 +10,6 @@ declare global {
|
|||||||
|
|
||||||
interface Note extends BaseNote {
|
interface Note extends BaseNote {
|
||||||
wordCount: number
|
wordCount: number
|
||||||
links: {
|
|
||||||
to: string[]
|
|
||||||
from: string[]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ViewMode {
|
interface ViewMode {
|
||||||
|
|||||||
26
src/utils/helpers.ts
Normal file
26
src/utils/helpers.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { formatDistance, format, isToday } from 'date-fns'
|
||||||
|
|
||||||
|
export const formatDate = (date: Date | number): string => {
|
||||||
|
const dateToFormat = ['number', 'string'].includes(typeof date)
|
||||||
|
? new Date(date)
|
||||||
|
: date
|
||||||
|
const dateDistanceInWords = formatDistance(dateToFormat, new Date()) + ' ago'
|
||||||
|
|
||||||
|
return isToday(date)
|
||||||
|
? dateDistanceInWords
|
||||||
|
: format(date, 'D MMMM [at] HH:mm ')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllMatches = (
|
||||||
|
regex: RegExp,
|
||||||
|
input: string
|
||||||
|
): RegExpExecArray[] => {
|
||||||
|
const matches = []
|
||||||
|
let m
|
||||||
|
do {
|
||||||
|
m = regex.exec(input)
|
||||||
|
// console.log(m)
|
||||||
|
if (m) matches.push(m)
|
||||||
|
} while (m)
|
||||||
|
return matches
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user