sync with local storage
This commit is contained in:
17
src/App.vue
17
src/App.vue
@@ -6,23 +6,15 @@ import { initialized } from '@/composables/useFirebase'
|
||||
import firebase from 'firebase/compat/app'
|
||||
import * as firebaseui from 'firebaseui'
|
||||
|
||||
const sideBarCollapsed = ref(windowIsMobile())
|
||||
const sideBarCollapsed = ref<boolean>(windowIsMobile())
|
||||
|
||||
const Note = defineAsyncComponent(() => import('@/components/ViewModes/Note.vue'))
|
||||
const ListView = defineAsyncComponent(() => import('@/components/ViewModes/ListView.vue'))
|
||||
const Mindmap = defineAsyncComponent(() => import('@/components/ViewModes/Mindmap.vue'))
|
||||
// const Note = defineAsyncComponent(() => import('@/components/ViewModes/Note.vue'))
|
||||
// const ListView = defineAsyncComponent(() => import('@/components/ViewModes/ListView.vue'))
|
||||
// const Mindmap = defineAsyncComponent(() => import('@/components/ViewModes/Mindmap.vue'))
|
||||
|
||||
const firebaseAuthUI =
|
||||
firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth())
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -54,7 +46,6 @@ provide('firebaseAuthUI', firebaseAuthUI)
|
||||
<Mindmap v-else-if="activeViewMode.name === 'Mindmap'" />
|
||||
</template>
|
||||
<SkeletonNote v-else />
|
||||
<!-- <SkeletonNote /> -->
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -38,7 +38,7 @@ const editorConfig = {
|
||||
ParagraphPlugin,
|
||||
ListPlugin,
|
||||
AutoformatPlugin,
|
||||
ContextedPlugin,
|
||||
ContextedPlugin
|
||||
],
|
||||
toolbar: {
|
||||
items: [
|
||||
@@ -51,10 +51,10 @@ const editorConfig = {
|
||||
'redo',
|
||||
'heading',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
],
|
||||
'numberedList'
|
||||
]
|
||||
},
|
||||
placeholder: 'Click here to start typing...',
|
||||
placeholder: 'Click here to start typing...'
|
||||
}
|
||||
|
||||
const editorElement = ref<HTMLInputElement | null>(null)
|
||||
@@ -70,10 +70,10 @@ const handleClick = ({ data }: { data: any }) => {
|
||||
}
|
||||
|
||||
const autocompleteRef = ref<InstanceType<typeof Autocomplete> | null>(null)
|
||||
const showAutocomplete = ref(false)
|
||||
const autocompleteStyle = ref({})
|
||||
const autocompleteText = ref('')
|
||||
const autocompleteReverse = ref(false)
|
||||
const showAutocomplete = ref<boolean>(false)
|
||||
const autocompleteStyle = ref<{ [key: string]: any }>({})
|
||||
const autocompleteText = ref<string>('')
|
||||
const autocompleteReverse = ref<boolean>(false)
|
||||
|
||||
const handleAutocomplete = async (event: AutocompleteEvent) => {
|
||||
const position = event.position
|
||||
@@ -84,7 +84,7 @@ const handleAutocomplete = async (event: AutocompleteEvent) => {
|
||||
)
|
||||
autocompleteStyle.value = {
|
||||
top: `${position.top - rect.top + lineHeight}px`,
|
||||
left: `${position.left - rect.left}px`,
|
||||
left: `${position.left - rect.left}px`
|
||||
}
|
||||
}
|
||||
autocompleteText.value = event.autocompleteText || ''
|
||||
@@ -99,12 +99,10 @@ const handleAutocomplete = async (event: AutocompleteEvent) => {
|
||||
editorRect &&
|
||||
autocompleteRect.bottom > editorRect.bottom
|
||||
) {
|
||||
const autocompleteHeight = parseFloat(
|
||||
window.getComputedStyle(autocompleteElem).height
|
||||
)
|
||||
const autocompleteHeight = parseFloat(window.getComputedStyle(autocompleteElem).height)
|
||||
autocompleteStyle.value = {
|
||||
...autocompleteStyle.value,
|
||||
top: `${position.top - editorRect.top - autocompleteHeight}px`,
|
||||
top: `${position.top - editorRect.top - autocompleteHeight}px`
|
||||
}
|
||||
autocompleteReverse.value = true
|
||||
} else {
|
||||
|
||||
@@ -5,7 +5,7 @@ const emit = defineEmits<{
|
||||
active: [active: boolean]
|
||||
}>()
|
||||
|
||||
const active = ref(false)
|
||||
const active = ref<boolean>(false)
|
||||
watch(active, () => {
|
||||
if (!active.value) {
|
||||
query.value = ''
|
||||
@@ -14,7 +14,7 @@ watch(active, () => {
|
||||
emit('active', active.value)
|
||||
})
|
||||
|
||||
const query = ref('')
|
||||
const query = ref<string>('')
|
||||
const results = computed<Note[]>(() => {
|
||||
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: []
|
||||
}>()
|
||||
|
||||
const searchActive = ref(false)
|
||||
const searchActive = ref<boolean>(false)
|
||||
|
||||
const signOut = async (close: () => Promise<boolean>) => {
|
||||
await firebaseSignOut()
|
||||
@@ -19,7 +19,7 @@ const signOut = async (close: () => Promise<boolean>) => {
|
||||
}
|
||||
|
||||
const authUI: any = inject('firebaseAuthUI')
|
||||
const authModalInitialStateOpen = ref(authUI.isPendingRedirect())
|
||||
const authModalInitialStateOpen = ref<boolean>(authUI.isPendingRedirect())
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
|
||||
@@ -10,7 +10,7 @@ const notesWithReferences = computed(() => {
|
||||
})
|
||||
|
||||
const selectedNotes = ref<{ [key: string]: Boolean }>({})
|
||||
const countSelectedNotes = computed(
|
||||
const countSelectedNotes = computed<number>(
|
||||
() => 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]
|
||||
}
|
||||
|
||||
const filter = ref('')
|
||||
const filter = ref<string>('')
|
||||
|
||||
const deleteSelectedNotes = (closeModal: () => void) => {
|
||||
closeModal()
|
||||
|
||||
@@ -17,7 +17,7 @@ const emit = defineEmits<{
|
||||
update: [note: Note]
|
||||
}>()
|
||||
|
||||
const noteTitle = ref(props.note.title)
|
||||
const noteTitle = ref<string>(props.note.title)
|
||||
watch(noteTitle, () => {
|
||||
const updatedNote: Note = { ...props.note, title: noteTitle.value }
|
||||
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()
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
import { defaultNotes } from '@/utils/defaultNotes'
|
||||
import { viewModes, activeViewMode } from '@/composables/useViewMode'
|
||||
import { useTitle } from '@vueuse/core'
|
||||
import { mdToHtml } from '@/utils/markdown'
|
||||
import { getAllMatches } from '@/utils/helpers'
|
||||
import { initialized, user } from '@/composables/useFirebase'
|
||||
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[]>(() => {
|
||||
return Object.entries(baseNotes)
|
||||
return Object.entries(baseNotes.value)
|
||||
.map(([, note]) => ({
|
||||
...note,
|
||||
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) => {
|
||||
if (rootNote.value) {
|
||||
const updatedRootNote = { ...baseNotes[rootNote.value.id], isRoot: false }
|
||||
const updatedRootNote = { ...baseNotes.value[rootNote.value.id], isRoot: false }
|
||||
updateNote(updatedRootNote.id, updatedRootNote)
|
||||
}
|
||||
const note = { ...baseNotes[noteId], isRoot: true }
|
||||
const note = { ...baseNotes.value[noteId], isRoot: true }
|
||||
updateNote(noteId, note)
|
||||
setActiveNote(noteId)
|
||||
}
|
||||
|
||||
export const setDefaultNotes = (defaultNotes: BaseNote[]) => {
|
||||
defaultNotes.forEach((defaultNote) => {
|
||||
baseNotes[defaultNote.id] = defaultNote
|
||||
baseNotes.value[defaultNote.id] = defaultNote
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,7 +107,7 @@ export const updateNote = (noteId: string, note: BaseNote) => {
|
||||
...note,
|
||||
modified: new Date().getTime()
|
||||
}
|
||||
baseNotes[noteId] = updatedNote
|
||||
baseNotes.value[noteId] = updatedNote
|
||||
}
|
||||
|
||||
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(),
|
||||
modified: new Date().getTime()
|
||||
}
|
||||
baseNotes[id] = newNote
|
||||
baseNotes.value[id] = newNote
|
||||
if (goToNote) setActiveNote(id)
|
||||
return newNote
|
||||
}
|
||||
|
||||
export const deleteNote = (noteId: string) => {
|
||||
delete baseNotes[noteId]
|
||||
delete baseNotes.value[noteId]
|
||||
}
|
||||
|
||||
const getNoteLinksByNoteId = (noteId: string): string[] => {
|
||||
const note = baseNotes[noteId]
|
||||
const note = baseNotes.value[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))
|
||||
.filter((noteId) => Object.keys(baseNotes.value).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 noteIds = Object.keys(baseNotes.value)
|
||||
const relations = noteIds
|
||||
.filter((id) => id !== undefined)
|
||||
.map((id) => {
|
||||
@@ -166,3 +175,22 @@ export function getNoteReferences(note: Note) {
|
||||
.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: '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 '@fortawesome/fontawesome-free/css/all.min.css'
|
||||
import App from './App.vue'
|
||||
import { setDefaultNotes } from '@/composables/useNotes'
|
||||
import { defaultNotes } from '@/utils/defaultNotes'
|
||||
import { usePreferredDark, useFavicon } from '@vueuse/core'
|
||||
import { initializeFirebase } from '@/composables/useFirebase'
|
||||
|
||||
initializeFirebase()
|
||||
|
||||
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)
|
||||
|
||||
setDefaultNotes(defaultNotes)
|
||||
|
||||
createApp(App).mount('#app')
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
@import '@fontsource/source-sans-pro/300';
|
||||
|
||||
html {
|
||||
height: fill-available;
|
||||
height: -webkit-fill-available;
|
||||
height: stretch;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
min-height: fill-available;
|
||||
min-height: -webkit-fill-available;
|
||||
min-height: stretch;
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
overflow-y: scroll;
|
||||
@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
|
||||
show: boolean
|
||||
}
|
||||
|
||||
interface NoteRelations {
|
||||
id: string
|
||||
to: string[]
|
||||
from: string[]
|
||||
}
|
||||
|
||||
interface NotesRelations {
|
||||
[noteId: string]: {
|
||||
to: string[]
|
||||
from: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
export {}
|
||||
|
||||
Reference in New Issue
Block a user