export functionality

This commit is contained in:
nicwands
2026-03-19 15:53:52 -04:00
parent 779c96e702
commit f9e7fe1208
8 changed files with 872 additions and 89 deletions

View File

@@ -2,14 +2,14 @@
<html>
<head>
<meta charset="UTF-8" />
<title>Electron</title>
<title>Taker of Notes</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/>
<script type="module" crossorigin src="./assets/index-D2TWwJ08.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-BFwBEQYI.css">
<script type="module" crossorigin src="./assets/index-dVpT1Jfp.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-s71dsOUL.css">
</head>
<body>

773
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -38,11 +38,13 @@
"@tiptap/starter-kit": "^3.19.0",
"@tiptap/vue-3": "^3.19.0",
"@vueuse/core": "^14.2.1",
"archiver": "^7.0.1",
"dotenv": "^17.3.1",
"electron-updater": "^6.3.9",
"fecha": "^4.2.3",
"flexsearch": "^0.8.212",
"gsap": "^3.14.2",
"jszip": "^3.10.1",
"libsodium-wrappers": "^0.8.2",
"lodash": "^4.17.23",
"lowlight": "^3.3.0",

View File

@@ -13,7 +13,7 @@
Instructio
</router-link>
<button class="menu-item">Import</button>
<button class="menu-item">Export</button>
<button class="menu-item" @click="handleExport">Export</button>
<router-link class="menu-item" to="/preferences">
Preferences
</router-link>
@@ -30,10 +30,12 @@ import { ref, watch } from 'vue'
import Nav from './Nav.vue'
import useMenu from '@/composables/useMenu'
import { useRoute } from 'vue-router'
import useExport from '@/composables/useExport'
const container = ref()
const { menuOpen, closeMenu } = useMenu()
const { exportAllNotes } = useExport()
// Close on click outside
onClickOutside(container, () => {
@@ -49,7 +51,10 @@ watch(
},
)
const openNewCategory = () => {}
const handleExport = async () => {
closeMenu()
await exportAllNotes()
}
</script>
<style lang="scss">

View File

@@ -9,20 +9,13 @@
</template>
<script setup>
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
import { TaskList, TaskItem } from '@tiptap/extension-list'
import { Highlight } from '@tiptap/extension-highlight'
import PageLoading from '@/components/PageLoading.vue'
import { Editor, EditorContent } from '@tiptap/vue-3'
import Document from '@tiptap/extension-document'
import { Placeholder } from '@tiptap/extensions'
import { all, createLowlight } from 'lowlight'
import useNotes from '@/composables/useNotes'
import StarterKit from '@tiptap/starter-kit'
import _debounce from 'lodash/debounce'
import { Markdown } from '@tiptap/markdown'
import EditorMenu from './Menu.vue'
import { extensions } from '@/libs/editorExtensions'
const props = defineProps({
id: {
@@ -58,40 +51,8 @@ const onUpdate = _debounce(async ({ editor }) => {
onMounted(async () => {
const note = await loadNote(props.id)
// Lowlight setup
const lowlight = createLowlight(all)
// Force note format
const CustomDocument = Document.extend({
content: 'heading block*',
})
editor.value = new Editor({
extensions: [
CustomDocument,
StarterKit.configure({
document: false,
heading: { levels: [1] },
trailingNode: {
node: 'paragraph',
},
}),
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === 'heading') {
return 'Title'
}
},
}),
TaskList,
TaskItem,
Highlight,
Markdown,
CodeBlockLowlight.configure({
lowlight,
enableTabIndentation: true,
}),
],
extensions,
content: note.content || [],
onUpdate: onUpdate,
})

View File

@@ -0,0 +1,61 @@
import JSZip from 'jszip'
import _kebabCase from 'lodash/kebabCase'
import { tipTapToMarkdown } from '@/libs/markdownConvert'
import useNotes from '@/composables/useNotes'
export default () => {
const { categories, loadCategoryNotes, loadCategories } = useNotes()
const sanitizeFilename = (name) => {
return _kebabCase(name) || 'untitled'
}
const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
const exportAllNotes = async () => {
// Make sure categories are loaded
await loadCategories()
// Init zip
const zip = new JSZip()
// Load uncategorized notes
const rootNotes = await loadCategoryNotes()
for (const note of rootNotes) {
console.log(note)
const markdown = tipTapToMarkdown(note.content)
const filename = `${sanitizeFilename(note.title)}.md`
zip.file(filename, markdown)
}
if (categories.value?.length) {
for (const category of categories.value) {
const notes = await loadCategoryNotes(category)
const categoryFolder = zip.folder(category)
for (const note of notes) {
const markdown = tipTapToMarkdown(note.content)
const filename = `${sanitizeFilename(note.title)}.md`
categoryFolder.file(filename, markdown)
}
}
}
const blob = await zip.generateAsync({ type: 'blob' })
const timestamp = new Date().toISOString().slice(0, 10)
downloadBlob(blob, `takerofnotes-export-${timestamp}.zip`)
}
return {
exportAllNotes,
}
}

View File

@@ -0,0 +1,42 @@
import { Markdown } from '@tiptap/markdown'
import StarterKit from '@tiptap/starter-kit'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import { TaskList, TaskItem } from '@tiptap/extension-list'
import { Highlight } from '@tiptap/extension-highlight'
import Document from '@tiptap/extension-document'
import { all, createLowlight } from 'lowlight'
import { Placeholder } from '@tiptap/extensions'
// Lowlight setup
const lowlight = createLowlight(all)
// Force note format
const CustomDocument = Document.extend({
content: 'heading block*',
})
export const extensions = [
CustomDocument,
StarterKit.configure({
document: false,
heading: { levels: [1] },
trailingNode: {
node: 'paragraph',
},
}),
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === 'heading') {
return 'Title'
}
},
}),
TaskList,
TaskItem,
Highlight,
Markdown,
CodeBlockLowlight.configure({
lowlight,
enableTabIndentation: true,
}),
]

View File

@@ -0,0 +1,25 @@
import { Editor } from '@tiptap/core'
import { extensions } from '@/libs/editorExtensions'
let editorInstance = null
const getEditorInstance = () => {
if (!editorInstance) {
editorInstance = new Editor({
extensions,
content: [],
})
}
return editorInstance
}
export const tipTapToMarkdown = (content) => {
if (!content) return ''
const editor = getEditorInstance()
editor.commands.setContent(content)
const markdown = editor.getMarkdown()
editor.commands.clearContent()
return markdown
}