sync with local storage

This commit is contained in:
2023-05-22 00:19:46 +02:00
parent 6dd8c2d524
commit c2cbe513d1
14 changed files with 95 additions and 67 deletions

1
components.d.ts vendored
View File

@@ -28,6 +28,7 @@ declare module '@vue/runtime-core' {
SkeletonNote: typeof import('./src/components/Skeleton/SkeletonNote.vue')['default'] SkeletonNote: typeof import('./src/components/Skeleton/SkeletonNote.vue')['default']
SkeletonSidebarItem: typeof import('./src/components/Skeleton/SkeletonSidebarItem.vue')['default'] SkeletonSidebarItem: typeof import('./src/components/Skeleton/SkeletonSidebarItem.vue')['default']
SkeletonTopBar: typeof import('./src/components/Skeleton/SkeletonTopBar.vue')['default'] SkeletonTopBar: typeof import('./src/components/Skeleton/SkeletonTopBar.vue')['default']
Spinner: typeof import('./src/components/Spinner.vue')['default']
TopBar: typeof import('./src/components/TopBar.vue')['default'] TopBar: typeof import('./src/components/TopBar.vue')['default']
} }
} }

View File

@@ -6,23 +6,15 @@ import { initialized } from '@/composables/useFirebase'
import firebase from 'firebase/compat/app' import firebase from 'firebase/compat/app'
import * as firebaseui from 'firebaseui' import * as firebaseui from 'firebaseui'
const sideBarCollapsed = ref(windowIsMobile()) const sideBarCollapsed = ref<boolean>(windowIsMobile())
const Note = defineAsyncComponent(() => import('@/components/ViewModes/Note.vue')) // const Note = defineAsyncComponent(() => import('@/components/ViewModes/Note.vue'))
const ListView = defineAsyncComponent(() => import('@/components/ViewModes/ListView.vue')) // const ListView = defineAsyncComponent(() => import('@/components/ViewModes/ListView.vue'))
const Mindmap = defineAsyncComponent(() => import('@/components/ViewModes/Mindmap.vue')) // const Mindmap = defineAsyncComponent(() => import('@/components/ViewModes/Mindmap.vue'))
const firebaseAuthUI = const firebaseAuthUI =
firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth()) firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth())
provide('firebaseAuthUI', firebaseAuthUI) provide('firebaseAuthUI', firebaseAuthUI)
// const setMinHeight = () => {
// const app = document.querySelector('#app')
// app?.setAttribute('style', `min-height: ${window.innerHeight}px`)
// }
// const handleResize = useDebounceFn(() => setMinHeight(), 100, { maxWait: 100 })
// window.addEventListener('resize', handleResize)
// setMinHeight()
</script> </script>
<template> <template>
@@ -54,7 +46,6 @@ provide('firebaseAuthUI', firebaseAuthUI)
<Mindmap v-else-if="activeViewMode.name === 'Mindmap'" /> <Mindmap v-else-if="activeViewMode.name === 'Mindmap'" />
</template> </template>
<SkeletonNote v-else /> <SkeletonNote v-else />
<!-- <SkeletonNote /> -->
</main> </main>
</div> </div>
</template> </template>

View File

@@ -38,7 +38,7 @@ const editorConfig = {
ParagraphPlugin, ParagraphPlugin,
ListPlugin, ListPlugin,
AutoformatPlugin, AutoformatPlugin,
ContextedPlugin, ContextedPlugin
], ],
toolbar: { toolbar: {
items: [ items: [
@@ -51,10 +51,10 @@ const editorConfig = {
'redo', 'redo',
'heading', 'heading',
'bulletedList', 'bulletedList',
'numberedList', 'numberedList'
], ]
}, },
placeholder: 'Click here to start typing...', placeholder: 'Click here to start typing...'
} }
const editorElement = ref<HTMLInputElement | null>(null) const editorElement = ref<HTMLInputElement | null>(null)
@@ -70,10 +70,10 @@ const handleClick = ({ data }: { data: any }) => {
} }
const autocompleteRef = ref<InstanceType<typeof Autocomplete> | null>(null) const autocompleteRef = ref<InstanceType<typeof Autocomplete> | null>(null)
const showAutocomplete = ref(false) const showAutocomplete = ref<boolean>(false)
const autocompleteStyle = ref({}) const autocompleteStyle = ref<{ [key: string]: any }>({})
const autocompleteText = ref('') const autocompleteText = ref<string>('')
const autocompleteReverse = ref(false) const autocompleteReverse = ref<boolean>(false)
const handleAutocomplete = async (event: AutocompleteEvent) => { const handleAutocomplete = async (event: AutocompleteEvent) => {
const position = event.position const position = event.position
@@ -84,7 +84,7 @@ const handleAutocomplete = async (event: AutocompleteEvent) => {
) )
autocompleteStyle.value = { autocompleteStyle.value = {
top: `${position.top - rect.top + lineHeight}px`, top: `${position.top - rect.top + lineHeight}px`,
left: `${position.left - rect.left}px`, left: `${position.left - rect.left}px`
} }
} }
autocompleteText.value = event.autocompleteText || '' autocompleteText.value = event.autocompleteText || ''
@@ -99,12 +99,10 @@ const handleAutocomplete = async (event: AutocompleteEvent) => {
editorRect && editorRect &&
autocompleteRect.bottom > editorRect.bottom autocompleteRect.bottom > editorRect.bottom
) { ) {
const autocompleteHeight = parseFloat( const autocompleteHeight = parseFloat(window.getComputedStyle(autocompleteElem).height)
window.getComputedStyle(autocompleteElem).height
)
autocompleteStyle.value = { autocompleteStyle.value = {
...autocompleteStyle.value, ...autocompleteStyle.value,
top: `${position.top - editorRect.top - autocompleteHeight}px`, top: `${position.top - editorRect.top - autocompleteHeight}px`
} }
autocompleteReverse.value = true autocompleteReverse.value = true
} else { } else {

View File

@@ -5,7 +5,7 @@ const emit = defineEmits<{
active: [active: boolean] active: [active: boolean]
}>() }>()
const active = ref(false) const active = ref<boolean>(false)
watch(active, () => { watch(active, () => {
if (!active.value) { if (!active.value) {
query.value = '' query.value = ''
@@ -14,7 +14,7 @@ watch(active, () => {
emit('active', active.value) emit('active', active.value)
}) })
const query = ref('') const query = ref<string>('')
const results = computed<Note[]>(() => { const results = computed<Note[]>(() => {
return query.value ? findNotes(query.value) : notes.value return query.value ? findNotes(query.value) : notes.value
}) })

View File

@@ -0,0 +1 @@
<template>loading...</template>

View File

@@ -11,7 +11,7 @@ const emit = defineEmits<{
toggleSideBar: [] toggleSideBar: []
}>() }>()
const searchActive = ref(false) const searchActive = ref<boolean>(false)
const signOut = async (close: () => Promise<boolean>) => { const signOut = async (close: () => Promise<boolean>) => {
await firebaseSignOut() await firebaseSignOut()
@@ -19,7 +19,7 @@ const signOut = async (close: () => Promise<boolean>) => {
} }
const authUI: any = inject('firebaseAuthUI') const authUI: any = inject('firebaseAuthUI')
const authModalInitialStateOpen = ref(authUI.isPendingRedirect()) const authModalInitialStateOpen = ref<boolean>(authUI.isPendingRedirect())
</script> </script>
<template> <template>
<div <div

View File

@@ -10,7 +10,7 @@ const notesWithReferences = computed(() => {
}) })
const selectedNotes = ref<{ [key: string]: Boolean }>({}) const selectedNotes = ref<{ [key: string]: Boolean }>({})
const countSelectedNotes = computed( const countSelectedNotes = computed<number>(
() => Object.entries(selectedNotes.value).filter(([, selected]) => Boolean(selected)).length () => Object.entries(selectedNotes.value).filter(([, selected]) => Boolean(selected)).length
) )
@@ -18,7 +18,7 @@ const toggleRow = (note: Note) => {
if (!note.isRoot) selectedNotes.value[note.id] = !selectedNotes.value[note.id] if (!note.isRoot) selectedNotes.value[note.id] = !selectedNotes.value[note.id]
} }
const filter = ref('') const filter = ref<string>('')
const deleteSelectedNotes = (closeModal: () => void) => { const deleteSelectedNotes = (closeModal: () => void) => {
closeModal() closeModal()

View File

@@ -17,7 +17,7 @@ const emit = defineEmits<{
update: [note: Note] update: [note: Note]
}>() }>()
const noteTitle = ref(props.note.title) const noteTitle = ref<string>(props.note.title)
watch(noteTitle, () => { watch(noteTitle, () => {
const updatedNote: Note = { ...props.note, title: noteTitle.value } const updatedNote: Note = { ...props.note, title: noteTitle.value }
emit('update', updatedNote) emit('update', updatedNote)

View File

@@ -29,6 +29,6 @@ export const initializeFirebase = () => {
}) })
} }
export const initialized = computed(() => user.value !== undefined) export const initialized = computed<boolean>(() => user.value !== undefined)
export const signOut = () => firebase.auth().signOut() export const signOut = () => firebase.auth().signOut()

View File

@@ -1,13 +1,34 @@
import { defaultNotes } from '@/utils/defaultNotes'
import { viewModes, activeViewMode } from '@/composables/useViewMode' import { viewModes, activeViewMode } from '@/composables/useViewMode'
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 { getAllMatches } from '@/utils/helpers'
import { initialized, user } from '@/composables/useFirebase'
import shortid from 'shortid' import shortid from 'shortid'
const baseNotes = reactive<{ [noteId: string]: BaseNote }>({}) const notesSource = computed<'firebase' | 'local' | null>(() => {
if (!initialized.value) return null
return user.value ? 'firebase' : 'local'
})
const baseNotes = ref<{ [noteId: string]: BaseNote }>({})
watch(
baseNotes,
() => {
console.log(`Sync base notes with ${notesSource.value}`, baseNotes.value)
if (!notesSource.value) return
if (notesSource.value === 'local') {
console.log('sync with local')
localStorage.setItem('notes', JSON.stringify(baseNotes.value))
} else if (notesSource.value === 'firebase') {
console.log('sync with firebase')
}
},
{ deep: true }
)
export const notes = computed<Note[]>(() => { export const notes = computed<Note[]>(() => {
return Object.entries(baseNotes) return Object.entries(baseNotes.value)
.map(([, note]) => ({ .map(([, note]) => ({
...note, ...note,
wordCount: note.content.split(' ').filter((word) => word.length > 0).length wordCount: note.content.split(' ').filter((word) => word.length > 0).length
@@ -40,17 +61,17 @@ export const rootNote = computed<Note | undefined>(() => {
export const setRootNote = (noteId: string) => { export const setRootNote = (noteId: string) => {
if (rootNote.value) { if (rootNote.value) {
const updatedRootNote = { ...baseNotes[rootNote.value.id], isRoot: false } const updatedRootNote = { ...baseNotes.value[rootNote.value.id], isRoot: false }
updateNote(updatedRootNote.id, updatedRootNote) updateNote(updatedRootNote.id, updatedRootNote)
} }
const note = { ...baseNotes[noteId], isRoot: true } const note = { ...baseNotes.value[noteId], isRoot: true }
updateNote(noteId, note) updateNote(noteId, note)
setActiveNote(noteId) setActiveNote(noteId)
} }
export const setDefaultNotes = (defaultNotes: BaseNote[]) => { export const setDefaultNotes = (defaultNotes: BaseNote[]) => {
defaultNotes.forEach((defaultNote) => { defaultNotes.forEach((defaultNote) => {
baseNotes[defaultNote.id] = defaultNote baseNotes.value[defaultNote.id] = defaultNote
}) })
} }
@@ -86,7 +107,7 @@ export const updateNote = (noteId: string, note: BaseNote) => {
...note, ...note,
modified: new Date().getTime() modified: new Date().getTime()
} }
baseNotes[noteId] = updatedNote baseNotes.value[noteId] = updatedNote
} }
export const addNote = (title: string, content: string, goToNote: boolean = false) => { export const addNote = (title: string, content: string, goToNote: boolean = false) => {
@@ -98,38 +119,26 @@ export const addNote = (title: string, content: string, goToNote: boolean = fals
created: new Date().getTime(), created: new Date().getTime(),
modified: new Date().getTime() modified: new Date().getTime()
} }
baseNotes[id] = newNote baseNotes.value[id] = newNote
if (goToNote) setActiveNote(id) if (goToNote) setActiveNote(id)
return newNote return newNote
} }
export const deleteNote = (noteId: string) => { export const deleteNote = (noteId: string) => {
delete baseNotes[noteId] delete baseNotes.value[noteId]
} }
const getNoteLinksByNoteId = (noteId: string): string[] => { const getNoteLinksByNoteId = (noteId: string): string[] => {
const note = baseNotes[noteId] const note = baseNotes.value[noteId]
const regex = /\[\[(.*?)\]\]/g const regex = /\[\[(.*?)\]\]/g
const links = getAllMatches(regex, note.content || '') const links = getAllMatches(regex, note.content || '')
.map((to) => notes.value.find((note) => note.title === to[1])?.id || '') .map((to) => notes.value.find((note) => note.title === to[1])?.id || '')
.filter((noteId) => Object.keys(baseNotes).includes(noteId)) .filter((noteId) => Object.keys(baseNotes.value).includes(noteId))
return [...links] return [...links]
} }
interface NoteRelations {
id: string
to: string[]
from: string[]
}
interface NotesRelations {
[noteId: string]: {
to: string[]
from: string[]
}
}
export const notesRelations = computed(() => { export const notesRelations = computed(() => {
const noteIds = Object.keys(baseNotes) const noteIds = Object.keys(baseNotes.value)
const relations = noteIds const relations = noteIds
.filter((id) => id !== undefined) .filter((id) => id !== undefined)
.map((id) => { .map((id) => {
@@ -166,3 +175,22 @@ export function getNoteReferences(note: Note) {
.filter((note): note is Note => note !== undefined) .filter((note): note is Note => note !== undefined)
: [] : []
} }
watch(
notesSource,
() => {
if (!notesSource.value) return
baseNotes.value = {}
if (notesSource.value === 'local') {
const localNotes = JSON.parse(localStorage.getItem('notes') || '{}')
baseNotes.value = localNotes
} else if (notesSource.value === 'firebase') {
console.log('get notes from firebase')
}
if (Object.entries(baseNotes.value).length === 0) {
setDefaultNotes(defaultNotes)
}
setActiveNote(rootNote.value?.id)
},
{ immediate: true }
)

View File

@@ -3,4 +3,4 @@ export const viewModes: ViewMode[] = [
{ name: 'List', icon: 'fas fa-list fa-fw' }, { name: 'List', icon: 'fas fa-list fa-fw' },
{ name: 'Mindmap', icon: 'fas fa-project-diagram fa-fw' }, { name: 'Mindmap', icon: 'fas fa-project-diagram fa-fw' },
] ]
export const activeViewMode = ref(viewModes[0]) export const activeViewMode = ref<ViewMode>(viewModes[0])

View File

@@ -2,17 +2,15 @@ import { createApp } from 'vue'
import '@/style.scss' import '@/style.scss'
import '@fortawesome/fontawesome-free/css/all.min.css' import '@fortawesome/fontawesome-free/css/all.min.css'
import App from './App.vue' import App from './App.vue'
import { setDefaultNotes } from '@/composables/useNotes'
import { defaultNotes } from '@/utils/defaultNotes'
import { usePreferredDark, useFavicon } from '@vueuse/core' import { usePreferredDark, useFavicon } from '@vueuse/core'
import { initializeFirebase } from '@/composables/useFirebase' import { initializeFirebase } from '@/composables/useFirebase'
initializeFirebase() initializeFirebase()
const isDark = usePreferredDark() const isDark = usePreferredDark()
const favicon = computed(() => (isDark.value ? '/contexted-white.ico' : '/contexted-black.ico')) const favicon = computed<string>(() =>
isDark.value ? '/contexted-white.ico' : '/contexted-black.ico'
)
useFavicon(favicon) useFavicon(favicon)
setDefaultNotes(defaultNotes)
createApp(App).mount('#app') createApp(App).mount('#app')

View File

@@ -5,14 +5,12 @@
@import '@fontsource/source-sans-pro/300'; @import '@fontsource/source-sans-pro/300';
html { html {
height: fill-available; height: stretch;
height: -webkit-fill-available;
} }
body { body {
min-height: 100vh; min-height: 100vh;
min-height: fill-available; min-height: stretch;
min-height: -webkit-fill-available;
font-family: 'Source Sans Pro', sans-serif; font-family: 'Source Sans Pro', sans-serif;
overflow-y: scroll; overflow-y: scroll;
@apply flex flex-col bg-gray-100; @apply flex flex-col bg-gray-100;

13
src/types.d.ts vendored
View File

@@ -23,5 +23,18 @@ declare global {
domElement?: HTMLElement domElement?: HTMLElement
show: boolean show: boolean
} }
interface NoteRelations {
id: string
to: string[]
from: string[]
}
interface NotesRelations {
[noteId: string]: {
to: string[]
from: string[]
}
}
} }
export {} export {}