export functionality
This commit is contained in:
@@ -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
773
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
61
src/renderer/src/composables/useExport.js
Normal file
61
src/renderer/src/composables/useExport.js
Normal 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,
|
||||
}
|
||||
}
|
||||
42
src/renderer/src/libs/editorExtensions.js
Normal file
42
src/renderer/src/libs/editorExtensions.js
Normal 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,
|
||||
}),
|
||||
]
|
||||
25
src/renderer/src/libs/markdownConvert.js
Normal file
25
src/renderer/src/libs/markdownConvert.js
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user