sync with local storage
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -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']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/App.vue
17
src/App.vue
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
1
src/components/Spinner.vue
Normal file
1
src/components/Spinner.vue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<template>loading...</template>
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
)
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
13
src/types.d.ts
vendored
@@ -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 {}
|
||||||
|
|||||||
Reference in New Issue
Block a user