├── .eslintrc.cjs ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .prettierrc.json ├── .stylelintrc.cjs ├── README.md ├── electron-builder.json5 ├── electron ├── electron-env.d.ts ├── main.ts └── preload.ts ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── public ├── img │ ├── icons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── android-chrome-maskable-192x192.png │ │ ├── android-chrome-maskable-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── icon.icns │ │ ├── icon.ico │ │ └── icon.png │ ├── og-image.jpg │ └── screenshot.png ├── robots.txt └── sql-wasm.wasm ├── src ├── App.vue ├── assets │ ├── css │ │ ├── basic.scss │ │ ├── style.scss │ │ └── variable.scss │ └── js │ │ ├── booklist.ts │ │ ├── highlight.ts │ │ ├── sql.ts │ │ ├── tools.ts │ │ └── type.ts ├── main.ts ├── style.css └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | parser: 'vue-eslint-parser', 7 | parserOptions: { 8 | parser: '@typescript-eslint/parser', 9 | ecmaVersion: 2020, 10 | sourceType: 'module', 11 | ecmaFeatures: { 12 | jsx: true, 13 | }, 14 | }, 15 | // 继承插件的规则配置 16 | extends: ['eslint:recommended', 'plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended'], 17 | rules: { 18 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 19 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 20 | 'vue/require-default-prop': 'off', 21 | 'vue/multi-word-component-names': 'off', 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: Setup pnpm 16 | uses: pnpm/action-setup@v2 17 | with: 18 | version: 'latest' 19 | 20 | - name: Install dependencies 21 | run: | 22 | pnpm install 23 | pnpm run build 24 | 25 | - name: Deploy 26 | uses: JamesIves/github-pages-deploy-action@v4 27 | with: 28 | GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 29 | BRANCH: gh-pages 30 | FOLDER: dist-page 31 | CLEAN: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .stylelintcache 27 | dist-electron 28 | dist-page 29 | release 30 | 31 | linux-unpacked 32 | win-unpacked 33 | mac 34 | builder-debug.yml 35 | builder-effective-config.yaml -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 150, 4 | "semi": false, 5 | "endOfLine": "lf", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /.stylelintrc.cjs: -------------------------------------------------------------------------------- 1 | const sortOrderSmacss = require('stylelint-config-property-sort-order-smacss/generate') 2 | 3 | module.exports = { 4 | root: true, 5 | extends: 'stylelint-config-recommended-vue', 6 | plugins: ['stylelint-order'], 7 | overrides: [ 8 | { 9 | files: ['**/*.scss'], 10 | customSyntax: 'postcss-scss', 11 | }, 12 | { 13 | files: ["*.vue", "**/*.vue"], 14 | customSyntax: 'postcss-html', 15 | }, 16 | ], 17 | rules: { 18 | 'order/properties-order': [sortOrderSmacss()], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kobo-book-exporter 2 | 3 | Export Kobo Book Highlight (.md) & Book List (.csv, .json & .md). 4 | 5 | [https://mollykannn.github.io/kobo-book-exporter](https://mollykannn.github.io/kobo-book-exporter) 6 | ![Screenshot](https://mollykannn.github.io/kobo-book-exporter/img/screenshot.png) 7 | 8 | Retrieved from [Kobo Exporter: 匯出 Kobo 電子書的書籍清單與註記資料 (劃線與筆記) | Vixual](http://www.vixual.net/blog/archives/117) 9 | 10 | ## 安裝 (Install) 11 | 12 | ```shell 13 | yarn install 14 | ``` 15 | 16 | ## 使用方法 (Usage) 17 | 18 | 建立檔案 (Create files) 19 | ```shell 20 | yarn run build 21 | ``` 22 | 23 | 運行 (Run) 24 | ```shell 25 | yarn run serve 26 | ``` 27 | 28 | Icons made by [Freepik](https://www.freepik.com) from [www.flaticon.com](https://www.flaticon.com/) -------------------------------------------------------------------------------- /electron-builder.json5: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://www.electron.build/configuration/configuration 3 | */ 4 | { 5 | "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", 6 | "appId": "com.kobo-book-exporter.app", 7 | "compression": "maximum", 8 | "asar": true, 9 | "productName": "Kobo Book Exporter", 10 | "directories": { 11 | "output": "release/${version}" 12 | }, 13 | "files": [ 14 | "dist", 15 | "dist-electron" 16 | ], 17 | "mac": { 18 | "target": [ 19 | "7z" 20 | ], 21 | "artifactName": "${productName}-Mac-${version}-Installer.${ext}", 22 | }, 23 | "win": { 24 | "target": [ 25 | { 26 | "target": "7z", 27 | "arch": [ 28 | "x64" 29 | ] 30 | } 31 | ], 32 | "artifactName": "${productName}-Windows-${version}-Setup.${ext}" 33 | }, 34 | "nsis": { 35 | "oneClick": false, 36 | "perMachine": false, 37 | "allowToChangeInstallationDirectory": true, 38 | "deleteAppDataOnUninstall": false 39 | }, 40 | "linux": { 41 | "target": [ 42 | "7z" 43 | ], 44 | "artifactName": "${productName}-Linux-${version}.${ext}" 45 | }, 46 | "directories":{ 47 | "buildResources": "public/img/icons", 48 | "output": "release" 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /electron/electron-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace NodeJS { 4 | interface ProcessEnv { 5 | /** 6 | * The built directory structure 7 | * 8 | * ```tree 9 | * ├─┬─┬ dist 10 | * │ │ └── index.html 11 | * │ │ 12 | * │ ├─┬ dist-electron 13 | * │ │ ├── main.js 14 | * │ │ └── preload.js 15 | * │ 16 | * ``` 17 | */ 18 | DIST: string 19 | /** /dist/ or /public/ */ 20 | VITE_PUBLIC: string 21 | } 22 | } 23 | 24 | // Used in Renderer process, expose in `preload.ts` 25 | interface Window { 26 | ipcRenderer: import('electron').IpcRenderer 27 | } 28 | -------------------------------------------------------------------------------- /electron/main.ts: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron' 2 | import path from 'node:path' 3 | 4 | // The built directory structure 5 | // 6 | // ├─┬─┬ dist 7 | // │ │ └── index.html 8 | // │ │ 9 | // │ ├─┬ dist-electron 10 | // │ │ ├── main.js 11 | // │ │ └── preload.js 12 | // │ 13 | process.env.DIST = path.join(__dirname, '../dist') 14 | process.env.VITE_PUBLIC = app.isPackaged ? process.env.DIST : path.join(process.env.DIST, '../public') 15 | 16 | 17 | let win: BrowserWindow | null 18 | // 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x 19 | const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'] 20 | 21 | function createWindow() { 22 | win = new BrowserWindow({ 23 | icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'), 24 | webPreferences: { 25 | preload: path.join(__dirname, 'preload.js'), 26 | }, 27 | }) 28 | 29 | // DevTools 30 | if (process.env.NODE_ENV == "development") { 31 | win.webContents.openDevTools(); 32 | } 33 | 34 | // Test active push message to Renderer-process. 35 | win.webContents.on('did-finish-load', () => { 36 | win?.webContents.send('main-process-message', (new Date).toLocaleString()) 37 | }) 38 | 39 | if (VITE_DEV_SERVER_URL) { 40 | win.loadURL(VITE_DEV_SERVER_URL) 41 | } else { 42 | // win.loadFile('dist/index.html') 43 | win.loadFile(path.join(process.env.DIST, 'index.html')) 44 | } 45 | } 46 | 47 | // Quit when all windows are closed, except on macOS. There, it's common 48 | // for applications and their menu bar to stay active until the user quits 49 | // explicitly with Cmd + Q. 50 | app.on('window-all-closed', () => { 51 | if (process.platform !== 'darwin') { 52 | app.quit() 53 | win = null 54 | } 55 | }) 56 | 57 | app.on('activate', () => { 58 | // On OS X it's common to re-create a window in the app when the 59 | // dock icon is clicked and there are no other windows open. 60 | if (BrowserWindow.getAllWindows().length === 0) { 61 | createWindow() 62 | } 63 | }) 64 | 65 | app.whenReady().then(createWindow) 66 | -------------------------------------------------------------------------------- /electron/preload.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer } from 'electron' 2 | 3 | // --------- Expose some API to the Renderer process --------- 4 | contextBridge.exposeInMainWorld('ipcRenderer', withPrototype(ipcRenderer)) 5 | 6 | // `exposeInMainWorld` can't detect attributes and methods of `prototype`, manually patching it. 7 | function withPrototype(obj: Record) { 8 | const protos = Object.getPrototypeOf(obj) 9 | 10 | for (const [key, value] of Object.entries(protos)) { 11 | if (Object.prototype.hasOwnProperty.call(obj, key)) continue 12 | 13 | if (typeof value === 'function') { 14 | // Some native APIs, like `NodeJS.EventEmitter['on']`, don't work in the Renderer process. Wrapping them into a function. 15 | obj[key] = function (...args: any) { 16 | return value.call(obj, ...args) 17 | } 18 | } else { 19 | obj[key] = value 20 | } 21 | } 22 | return obj 23 | } 24 | 25 | // --------- Preload scripts loading --------- 26 | function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { 27 | return new Promise(resolve => { 28 | if (condition.includes(document.readyState)) { 29 | resolve(true) 30 | } else { 31 | document.addEventListener('readystatechange', () => { 32 | if (condition.includes(document.readyState)) { 33 | resolve(true) 34 | } 35 | }) 36 | } 37 | }) 38 | } 39 | 40 | const safeDOM = { 41 | append(parent: HTMLElement, child: HTMLElement) { 42 | if (!Array.from(parent.children).find(e => e === child)) { 43 | parent.appendChild(child) 44 | } 45 | }, 46 | remove(parent: HTMLElement, child: HTMLElement) { 47 | if (Array.from(parent.children).find(e => e === child)) { 48 | parent.removeChild(child) 49 | } 50 | }, 51 | } 52 | 53 | /** 54 | * https://tobiasahlin.com/spinkit 55 | * https://connoratherton.com/loaders 56 | * https://projects.lukehaas.me/css-loaders 57 | * https://matejkustec.github.io/SpinThatShit 58 | */ 59 | function useLoading() { 60 | const className = `loaders-css__square-spin` 61 | const styleContent = ` 62 | @keyframes square-spin { 63 | 25% { transform: perspective(100px) rotateX(180deg) rotateY(0); } 64 | 50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); } 65 | 75% { transform: perspective(100px) rotateX(0) rotateY(180deg); } 66 | 100% { transform: perspective(100px) rotateX(0) rotateY(0); } 67 | } 68 | .${className} > div { 69 | animation-fill-mode: both; 70 | width: 50px; 71 | height: 50px; 72 | background: #fff; 73 | animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; 74 | } 75 | .app-loading-wrap { 76 | position: fixed; 77 | top: 0; 78 | left: 0; 79 | width: 100vw; 80 | height: 100vh; 81 | display: flex; 82 | align-items: center; 83 | justify-content: center; 84 | background: #282c34; 85 | z-index: 9; 86 | } 87 | ` 88 | const oStyle = document.createElement('style') 89 | const oDiv = document.createElement('div') 90 | 91 | oStyle.id = 'app-loading-style' 92 | oStyle.innerHTML = styleContent 93 | oDiv.className = 'app-loading-wrap' 94 | oDiv.innerHTML = `
` 95 | 96 | return { 97 | appendLoading() { 98 | safeDOM.append(document.head, oStyle) 99 | safeDOM.append(document.body, oDiv) 100 | }, 101 | removeLoading() { 102 | safeDOM.remove(document.head, oStyle) 103 | safeDOM.remove(document.body, oDiv) 104 | }, 105 | } 106 | } 107 | 108 | // ---------------------------------------------------------------------- 109 | 110 | const { appendLoading, removeLoading } = useLoading() 111 | domReady().then(appendLoading) 112 | 113 | window.onmessage = ev => { 114 | ev.data.payload === 'removeLoading' && removeLoading() 115 | } 116 | 117 | setTimeout(removeLoading, 4999) 118 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Kobo Book Exporter 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./src/**/*" 4 | ] 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "private": true, 4 | "version": "1.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vue-tsc && vite build", 8 | "build:electron:mac": "vue-tsc && vite build --mode electron && electron-builder --mac --config", 9 | "build:electron:win": "vue-tsc && vite build --mode electron && electron-builder --win --config", 10 | "build:electron:linux": "vue-tsc && vite build --mode electron && electron-builder --linux --config", 11 | "build:electron:all": "vue-tsc && vite build --mode electron && electron-builder --mac --win --linux --config", 12 | "preview": "vite preview" 13 | }, 14 | "dependencies": { 15 | "sql.js": "^1.9.0", 16 | "vue": "^3.3.13" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^20.10.5", 20 | "@types/sql.js": "^1.4.9", 21 | "@typescript-eslint/eslint-plugin": "^6.15.0", 22 | "@typescript-eslint/parser": "^6.15.0", 23 | "@vitejs/plugin-vue": "^4.5.2", 24 | "electron": "^28.0.0", 25 | "electron-builder": "^24.9.1", 26 | "eslint": "^8.56.0", 27 | "eslint-config-prettier": "^9.1.0", 28 | "eslint-plugin-prettier": "^5.1.0", 29 | "eslint-plugin-vue": "^9.19.2", 30 | "postcss": "^8.4.32", 31 | "postcss-html": "^1.5.0", 32 | "postcss-scss": "^4.0.9", 33 | "prettier": "^3.1.1", 34 | "sass": "^1.69.5", 35 | "stylelint": "^16.0.2", 36 | "stylelint-config-property-sort-order-smacss": "^10.0.0", 37 | "stylelint-config-recommended-vue": "^1.5.0", 38 | "stylelint-order": "^6.0.4", 39 | "typescript": "^5.3.3", 40 | "vite": "^5.0.10", 41 | "vite-plugin-electron": "^0.15.5", 42 | "vite-plugin-electron-renderer": "^0.14.5", 43 | "vite-plugin-eslint": "^1.8.1", 44 | "vite-plugin-pwa": "^0.17.4", 45 | "vite-plugin-stylelint": "^5.3.1", 46 | "vue-tsc": "^1.8.25" 47 | }, 48 | "main": "dist-electron/main.js" 49 | } -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/icon.icns -------------------------------------------------------------------------------- /public/img/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/icon.ico -------------------------------------------------------------------------------- /public/img/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/icons/icon.png -------------------------------------------------------------------------------- /public/img/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/og-image.jpg -------------------------------------------------------------------------------- /public/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/img/screenshot.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/sql-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollykannn/kobo-book-exporter/7b4045dc70248ba679d290c2f4b22debd9c7e15f/public/sql-wasm.wasm -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 38 |