export functionality
This commit is contained in:
@@ -2,14 +2,14 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Electron</title>
|
<title>Taker of Notes</title>
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
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>
|
<script type="module" crossorigin src="./assets/index-dVpT1Jfp.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-BFwBEQYI.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-s71dsOUL.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<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/starter-kit": "^3.19.0",
|
||||||
"@tiptap/vue-3": "^3.19.0",
|
"@tiptap/vue-3": "^3.19.0",
|
||||||
"@vueuse/core": "^14.2.1",
|
"@vueuse/core": "^14.2.1",
|
||||||
|
"archiver": "^7.0.1",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"electron-updater": "^6.3.9",
|
"electron-updater": "^6.3.9",
|
||||||
"fecha": "^4.2.3",
|
"fecha": "^4.2.3",
|
||||||
"flexsearch": "^0.8.212",
|
"flexsearch": "^0.8.212",
|
||||||
"gsap": "^3.14.2",
|
"gsap": "^3.14.2",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
"libsodium-wrappers": "^0.8.2",
|
"libsodium-wrappers": "^0.8.2",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"lowlight": "^3.3.0",
|
"lowlight": "^3.3.0",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
Instructio
|
Instructio
|
||||||
</router-link>
|
</router-link>
|
||||||
<button class="menu-item">Import</button>
|
<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">
|
<router-link class="menu-item" to="/preferences">
|
||||||
Preferences
|
Preferences
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -30,10 +30,12 @@ import { ref, watch } from 'vue'
|
|||||||
import Nav from './Nav.vue'
|
import Nav from './Nav.vue'
|
||||||
import useMenu from '@/composables/useMenu'
|
import useMenu from '@/composables/useMenu'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import useExport from '@/composables/useExport'
|
||||||
|
|
||||||
const container = ref()
|
const container = ref()
|
||||||
|
|
||||||
const { menuOpen, closeMenu } = useMenu()
|
const { menuOpen, closeMenu } = useMenu()
|
||||||
|
const { exportAllNotes } = useExport()
|
||||||
|
|
||||||
// Close on click outside
|
// Close on click outside
|
||||||
onClickOutside(container, () => {
|
onClickOutside(container, () => {
|
||||||
@@ -49,7 +51,10 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const openNewCategory = () => {}
|
const handleExport = async () => {
|
||||||
|
closeMenu()
|
||||||
|
await exportAllNotes()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -9,20 +9,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
|
|
||||||
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
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 PageLoading from '@/components/PageLoading.vue'
|
||||||
import { Editor, EditorContent } from '@tiptap/vue-3'
|
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 useNotes from '@/composables/useNotes'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
|
||||||
import _debounce from 'lodash/debounce'
|
import _debounce from 'lodash/debounce'
|
||||||
import { Markdown } from '@tiptap/markdown'
|
|
||||||
import EditorMenu from './Menu.vue'
|
import EditorMenu from './Menu.vue'
|
||||||
|
import { extensions } from '@/libs/editorExtensions'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
@@ -58,40 +51,8 @@ const onUpdate = _debounce(async ({ editor }) => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const note = await loadNote(props.id)
|
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({
|
editor.value = new Editor({
|
||||||
extensions: [
|
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,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
content: note.content || [],
|
content: note.content || [],
|
||||||
onUpdate: onUpdate,
|
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