feat: 初始化 kenaito-notebook 桌面记事本项目
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs,
|
||||
check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>kenaito-notebook</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./src/main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Generated
+2399
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^2.7.14",
|
||||
"element-ui": "^2.15.14",
|
||||
"mavon-editor": "^2.10.4",
|
||||
"highlight.js": "^11.9.0",
|
||||
"marked": "^9.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue2": "^2.3.1",
|
||||
"vite": "^3.0.7",
|
||||
"vite-plugin-vue2": "^2.0.3"
|
||||
}
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
2bd2793b3ea7c36dd202ef834837c1bb
|
||||
@@ -0,0 +1,938 @@
|
||||
<template>
|
||||
<div id="app" class="app-container">
|
||||
<!-- 未登录:显示登录页面 -->
|
||||
<login-view v-if="!isLoggedIn" @login-success="onLoginSuccess" />
|
||||
|
||||
<!-- 已登录:显示主界面 -->
|
||||
<el-container v-else style="height: 100vh">
|
||||
<el-aside width="280px" class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div class="title-row">
|
||||
<el-dropdown trigger="click" @command="handleUserCommand">
|
||||
<span class="user-info">
|
||||
<i class="el-icon-user-solid"></i>
|
||||
{{ email }}
|
||||
<i class="el-icon-arrow-down"></i>
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="logout">
|
||||
<i class="el-icon-switch-button"></i> 退出登录
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索笔记..."
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
@input="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<note-tree
|
||||
:notes="searchKeyword ? searchResults : noteTree"
|
||||
:selected-id="selectedNoteId"
|
||||
@select="openNote"
|
||||
@delete="deleteNote"
|
||||
@context-menu="handleContextMenu"
|
||||
/>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<el-button
|
||||
:type="showTrash ? 'primary' : 'text'"
|
||||
icon="el-icon-delete"
|
||||
@click="openTrash"
|
||||
>
|
||||
回收站
|
||||
</el-button>
|
||||
</div>
|
||||
</el-aside>
|
||||
|
||||
<el-main class="main-content">
|
||||
<!-- 回收站视图 -->
|
||||
<trash-view
|
||||
v-if="showTrash"
|
||||
:trash-notes="trashNotes"
|
||||
@restore="restoreNote"
|
||||
@permanent-delete="permanentDeleteNote"
|
||||
@empty-trash="emptyTrash"
|
||||
@close="showTrash = false"
|
||||
/>
|
||||
|
||||
<!-- 多标签页 -->
|
||||
<div v-else-if="tabs.length > 0" class="tabs-container">
|
||||
<div class="tabs-header">
|
||||
<div
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="tab-item"
|
||||
:class="{ 'is-active': activeTabId === tab.id }"
|
||||
@click="switchTab(tab.id)"
|
||||
>
|
||||
<span class="tab-title">{{ tab.title || '未命名' }}</span>
|
||||
<span class="tab-close" @click.stop="closeTab(tab.id)">
|
||||
<i class="el-icon-close"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑器内容 -->
|
||||
<div v-if="activeTab" class="editor-container">
|
||||
<div class="editor-header">
|
||||
<el-input
|
||||
v-model="activeTab.title"
|
||||
class="note-title-input"
|
||||
@blur="saveTab"
|
||||
placeholder="笔记标题"
|
||||
/>
|
||||
<div class="editor-actions">
|
||||
<el-dropdown trigger="click" @command="handleExport">
|
||||
<el-button size="small" icon="el-icon-download">
|
||||
导出 <i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="html">导出 HTML</el-dropdown-item>
|
||||
<el-dropdown-item command="markdown">导出 Markdown</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mavon-editor
|
||||
ref="md"
|
||||
v-model="activeTab.content"
|
||||
:toolbars="toolbars"
|
||||
:imageUpload="imageUpload"
|
||||
@change="onContentChange"
|
||||
@imgAdd="handleImgAdd"
|
||||
@imgDel="handleImgDel"
|
||||
style="height: calc(100vh - 180px)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="!showTrash" class="empty-state">
|
||||
<i class="el-icon-document"></i>
|
||||
<p>选择或创建一个笔记开始编写</p>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NoteTree from './components/NoteTree.vue'
|
||||
import TrashView from './components/TrashView.vue'
|
||||
import LoginView from './components/LoginView.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
NoteTree,
|
||||
TrashView,
|
||||
LoginView
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoggedIn: false,
|
||||
email: '',
|
||||
noteTree: [],
|
||||
tabs: [], // 打开的标签页列表
|
||||
activeTabId: null, // 当前激活的标签页 ID
|
||||
searchKeyword: '',
|
||||
searchResults: [],
|
||||
saveTimers: {}, // 每个标签页的保存定时器
|
||||
showTrash: false, // 是否显示回收站
|
||||
trashNotes: [], // 回收站中的笔记
|
||||
toolbars: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
header: true,
|
||||
underline: true,
|
||||
strikethrough: true,
|
||||
mark: true,
|
||||
quote: true,
|
||||
ol: true,
|
||||
ul: true,
|
||||
link: true,
|
||||
imagelink: true,
|
||||
code: true,
|
||||
table: true,
|
||||
fullscreen: true,
|
||||
htmlcode: true,
|
||||
undo: true,
|
||||
redo: true,
|
||||
preview: true,
|
||||
subfield: true,
|
||||
help: true
|
||||
},
|
||||
imageUpload: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedNoteId() {
|
||||
return this.activeTabId
|
||||
},
|
||||
activeTab() {
|
||||
return this.tabs.find(tab => tab.id === this.activeTabId)
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
// 检查登录状态
|
||||
try {
|
||||
const loggedIn = await window.go.main.App.IsLoggedIn()
|
||||
if (loggedIn) {
|
||||
this.isLoggedIn = true
|
||||
await this.loadUserInfo()
|
||||
await this.loadNoteTree()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to check login status:', err)
|
||||
}
|
||||
|
||||
// 监听全局粘贴和拖放事件
|
||||
document.addEventListener('paste', this.handlePaste)
|
||||
document.addEventListener('dragover', this.handleDragOver, true)
|
||||
document.addEventListener('drop', this.handleDrop, true)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('paste', this.handlePaste)
|
||||
document.removeEventListener('dragover', this.handleDragOver, true)
|
||||
document.removeEventListener('drop', this.handleDrop, true)
|
||||
},
|
||||
methods: {
|
||||
async onLoginSuccess() {
|
||||
this.isLoggedIn = true
|
||||
await this.loadUserInfo()
|
||||
await this.loadNoteTree()
|
||||
},
|
||||
|
||||
async loadUserInfo() {
|
||||
try {
|
||||
const user = await window.go.main.App.GetUser()
|
||||
this.email = user.email || ''
|
||||
} catch (err) {
|
||||
console.error('Failed to load user info:', err)
|
||||
}
|
||||
},
|
||||
|
||||
handleUserCommand(command) {
|
||||
if (command === 'logout') {
|
||||
this.$confirm('确定要退出登录吗?', '提示', {
|
||||
type: 'info',
|
||||
confirmButtonText: '退出',
|
||||
cancelButtonText: '取消'
|
||||
}).then(() => {
|
||||
window.go.main.App.Logout()
|
||||
this.isLoggedIn = false
|
||||
this.email = ''
|
||||
this.tabs = []
|
||||
this.activeTabId = null
|
||||
this.noteTree = []
|
||||
this.$message.success('已退出登录')
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
|
||||
async loadNoteTree() {
|
||||
try {
|
||||
this.noteTree = await window.go.main.App.GetNoteTree()
|
||||
} catch (err) {
|
||||
console.error('Failed to load note tree:', err)
|
||||
this.$message.error('加载笔记失败')
|
||||
}
|
||||
},
|
||||
|
||||
// 打开笔记到标签页
|
||||
async openNote(note) {
|
||||
if (note.isFolder) return
|
||||
|
||||
// 检查是否已经在标签页中
|
||||
const existingTab = this.tabs.find(tab => tab.id === note.id)
|
||||
if (existingTab) {
|
||||
this.activeTabId = note.id
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const fullNote = await window.go.main.App.GetNote(note.id)
|
||||
this.tabs.push({
|
||||
id: fullNote.id,
|
||||
title: fullNote.title,
|
||||
content: fullNote.content,
|
||||
parentId: fullNote.parentId,
|
||||
isDirty: false
|
||||
})
|
||||
this.activeTabId = fullNote.id
|
||||
} catch (err) {
|
||||
console.error('Failed to load note:', err)
|
||||
this.$message.error('加载笔记内容失败')
|
||||
}
|
||||
},
|
||||
|
||||
// 切换标签页
|
||||
switchTab(tabId) {
|
||||
this.activeTabId = tabId
|
||||
},
|
||||
|
||||
// 关闭标签页
|
||||
closeTab(tabId) {
|
||||
const tab = this.tabs.find(t => t.id === tabId)
|
||||
if (tab && tab.isDirty) {
|
||||
this.$confirm('有未保存的更改,确定要关闭吗?', '提示', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.removeTab(tabId)
|
||||
}).catch(() => {})
|
||||
} else {
|
||||
this.removeTab(tabId)
|
||||
}
|
||||
},
|
||||
|
||||
removeTab(tabId) {
|
||||
const index = this.tabs.findIndex(t => t.id === tabId)
|
||||
this.tabs.splice(index, 1)
|
||||
|
||||
if (this.activeTabId === tabId) {
|
||||
// 切换到相邻的标签页
|
||||
if (this.tabs.length > 0) {
|
||||
const newIndex = Math.min(index, this.tabs.length - 1)
|
||||
this.activeTabId = this.tabs[newIndex].id
|
||||
} else {
|
||||
this.activeTabId = null
|
||||
}
|
||||
}
|
||||
|
||||
// 清理定时器
|
||||
if (this.saveTimers[tabId]) {
|
||||
clearTimeout(this.saveTimers[tabId])
|
||||
delete this.saveTimers[tabId]
|
||||
}
|
||||
},
|
||||
|
||||
async createNoteDirectly(parentId, isFolder) {
|
||||
try {
|
||||
const title = isFolder ? '未命名文件夹' : '未命名笔记'
|
||||
const newNote = await window.go.main.App.CreateNote(parentId, title, isFolder)
|
||||
await this.loadNoteTree()
|
||||
|
||||
// 如果是新建笔记,自动打开
|
||||
if (!isFolder) {
|
||||
await this.openNote(newNote)
|
||||
}
|
||||
|
||||
this.$message.success('创建成功')
|
||||
} catch (err) {
|
||||
console.error('Failed to create note:', err)
|
||||
this.$message.error('创建失败')
|
||||
}
|
||||
},
|
||||
|
||||
async deleteNote(note) {
|
||||
// 如果标签页中有该笔记,先关闭
|
||||
const tabIndex = this.tabs.findIndex(t => t.id === note.id)
|
||||
if (tabIndex > -1) {
|
||||
this.removeTab(note.id)
|
||||
}
|
||||
|
||||
try {
|
||||
await window.go.main.App.DeleteNote(note.id)
|
||||
await this.loadNoteTree()
|
||||
if (this.showTrash) await this.loadTrashNotes()
|
||||
this.$message.success('已移到回收站')
|
||||
} catch (err) {
|
||||
console.error('Failed to delete note:', err)
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
},
|
||||
|
||||
saveTab() {
|
||||
if (!this.activeTab) return
|
||||
|
||||
if (this.saveTimers[this.activeTabId]) {
|
||||
clearTimeout(this.saveTimers[this.activeTabId])
|
||||
}
|
||||
|
||||
this.saveTimers[this.activeTabId] = setTimeout(async () => {
|
||||
try {
|
||||
await window.go.main.App.UpdateNote(
|
||||
this.activeTab.id,
|
||||
this.activeTab.title,
|
||||
this.activeTab.content,
|
||||
''
|
||||
)
|
||||
this.activeTab.isDirty = false
|
||||
await this.loadNoteTree()
|
||||
} catch (err) {
|
||||
console.error('Failed to save note:', err)
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
|
||||
onContentChange() {
|
||||
if (this.activeTab) {
|
||||
this.activeTab.isDirty = true
|
||||
}
|
||||
this.saveTab()
|
||||
},
|
||||
|
||||
// 粘贴事件处理
|
||||
async handlePaste(e) {
|
||||
if (!this.activeTab) return
|
||||
|
||||
const items = e.clipboardData?.items
|
||||
if (!items) return
|
||||
|
||||
// 检查是否有文件数据
|
||||
const files = []
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].kind === 'file') {
|
||||
const file = items[i].getAsFile()
|
||||
if (file) files.push(file)
|
||||
}
|
||||
}
|
||||
|
||||
if (files.length === 0) return
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
for (const file of files) {
|
||||
const maxSize = 6 * 1024 * 1024 // 6MB
|
||||
if (file.size > maxSize) {
|
||||
this.$message.warning(`文件 ${file.name} 大小超过 6MB,已跳过`)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
let base64Data
|
||||
const isImage = /\.(jpg|jpeg|png|gif|webp)$/i.test(file.name)
|
||||
if (isImage) {
|
||||
base64Data = await this.compressImage(file)
|
||||
} else {
|
||||
base64Data = await this.fileToBase64(file)
|
||||
}
|
||||
const fileUrl = await window.go.main.App.SaveFile(base64Data, file.name)
|
||||
this.insertFileLink(file.name, fileUrl)
|
||||
this.$message.success(`${file.name} 嵌入成功`)
|
||||
} catch (err) {
|
||||
console.error('Failed to upload file:', err)
|
||||
this.$message.error(`${file.name} 上传失败`)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 拖放事件处理(全局 capture 阶段)
|
||||
handleDragOver(e) {
|
||||
if (!this.activeTab) return
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
},
|
||||
|
||||
async handleDrop(e) {
|
||||
if (!this.activeTab) return
|
||||
|
||||
const files = e.dataTransfer?.files
|
||||
if (!files || files.length === 0) return
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
for (const file of files) {
|
||||
const maxSize = 6 * 1024 * 1024 // 6MB
|
||||
if (file.size > maxSize) {
|
||||
this.$message.warning(`文件 ${file.name} 大小超过 6MB,已跳过`)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
let base64Data
|
||||
const isImage = /\.(jpg|jpeg|png|gif|webp)$/i.test(file.name)
|
||||
if (isImage) {
|
||||
base64Data = await this.compressImage(file)
|
||||
} else {
|
||||
base64Data = await this.fileToBase64(file)
|
||||
}
|
||||
const fileUrl = await window.go.main.App.SaveFile(base64Data, file.name)
|
||||
this.insertFileLink(file.name, fileUrl)
|
||||
this.$message.success(`${file.name} 嵌入成功`)
|
||||
} catch (err) {
|
||||
console.error('Failed to upload file:', err)
|
||||
this.$message.error(`${file.name} 上传失败`)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
fileToBase64(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => resolve(reader.result)
|
||||
reader.onerror = error => reject(error)
|
||||
})
|
||||
},
|
||||
|
||||
insertFileLink(filename, filePath) {
|
||||
if (!this.activeTab) return
|
||||
|
||||
// 根据文件类型决定插入格式
|
||||
const isImage = /\.(jpg|jpeg|png|gif|webp|svg)$/i.test(filename)
|
||||
let markdown = ''
|
||||
if (isImage) {
|
||||
markdown = ``
|
||||
} else {
|
||||
markdown = `[${filename}](${filePath})`
|
||||
}
|
||||
|
||||
// 在内容末尾插入
|
||||
this.activeTab.content += `\n\n${markdown}\n`
|
||||
this.saveTab()
|
||||
},
|
||||
|
||||
async handleSearch() {
|
||||
if (!this.searchKeyword.trim()) {
|
||||
this.searchResults = []
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.searchResults = await window.go.main.App.SearchNotes(this.searchKeyword)
|
||||
} catch (err) {
|
||||
console.error('Failed to search notes:', err)
|
||||
}
|
||||
},
|
||||
|
||||
async handleExport(command) {
|
||||
if (!this.activeTab) return
|
||||
|
||||
try {
|
||||
let filePath
|
||||
if (command === 'html') {
|
||||
filePath = await window.go.main.App.ExportHTML(
|
||||
this.activeTab.title,
|
||||
this.activeTab.content
|
||||
)
|
||||
} else {
|
||||
filePath = await window.go.main.App.ExportMarkdown(
|
||||
this.activeTab.title,
|
||||
this.activeTab.content
|
||||
)
|
||||
}
|
||||
|
||||
if (filePath) {
|
||||
this.$confirm('导出成功!是否打开文件所在目录?', '导出完成', {
|
||||
confirmButtonText: '打开目录',
|
||||
cancelButtonText: '确定',
|
||||
type: 'success'
|
||||
}).then(() => {
|
||||
window.go.main.App.RevealInFinder(filePath)
|
||||
}).catch(() => {})
|
||||
}
|
||||
} catch (err) {
|
||||
// 用户取消选择不显示错误
|
||||
if (err && err.includes && err.includes('用户取消')) {
|
||||
return
|
||||
}
|
||||
console.error('Failed to export:', err)
|
||||
this.$message.error('导出失败')
|
||||
}
|
||||
},
|
||||
|
||||
async exportFolder(folder) {
|
||||
try {
|
||||
const filePath = await window.go.main.App.ExportFolderMarkdown(folder.id)
|
||||
if (filePath) {
|
||||
this.$confirm('导出成功!是否打开文件所在目录?', '导出完成', {
|
||||
confirmButtonText: '打开目录',
|
||||
cancelButtonText: '确定',
|
||||
type: 'success'
|
||||
}).then(() => {
|
||||
window.go.main.App.RevealInFinder(filePath)
|
||||
}).catch(() => {})
|
||||
}
|
||||
} catch (err) {
|
||||
if (err && err.includes && err.includes('用户取消')) {
|
||||
return
|
||||
}
|
||||
console.error('Failed to export folder:', err)
|
||||
this.$message.error('导出失败')
|
||||
}
|
||||
},
|
||||
|
||||
async exportAll() {
|
||||
try {
|
||||
const filePath = await window.go.main.App.ExportAllMarkdown()
|
||||
if (filePath) {
|
||||
this.$confirm('导出成功!是否打开文件所在目录?', '导出完成', {
|
||||
confirmButtonText: '打开目录',
|
||||
cancelButtonText: '确定',
|
||||
type: 'success'
|
||||
}).then(() => {
|
||||
window.go.main.App.RevealInFinder(filePath)
|
||||
}).catch(() => {})
|
||||
}
|
||||
} catch (err) {
|
||||
if (err && err.includes && err.includes('用户取消')) {
|
||||
return
|
||||
}
|
||||
console.error('Failed to export all:', err)
|
||||
this.$message.error('导出失败')
|
||||
}
|
||||
},
|
||||
|
||||
handleContextMenu(event) {
|
||||
const { command, note } = event
|
||||
if (command === 'newChild') {
|
||||
this.createNoteDirectly(note.id, false)
|
||||
} else if (command === 'newFolder') {
|
||||
this.createNoteDirectly(note ? note.id : 0, true)
|
||||
} else if (command === 'newNote') {
|
||||
this.createNoteDirectly(0, false)
|
||||
} else if (command === 'delete') {
|
||||
this.deleteNote(note)
|
||||
} else if (command === 'trash') {
|
||||
this.showTrash = true
|
||||
} else if (command === 'renamed') {
|
||||
this.loadNoteTree()
|
||||
} else if (command === 'exportFolder') {
|
||||
this.exportFolder(note)
|
||||
} else if (command === 'exportAll') {
|
||||
this.exportAll()
|
||||
}
|
||||
},
|
||||
|
||||
// 回收站相关方法
|
||||
async openTrash() {
|
||||
this.showTrash = !this.showTrash
|
||||
},
|
||||
|
||||
async loadTrashNotes() {
|
||||
try {
|
||||
const notes = await window.go.main.App.GetTrashNotes()
|
||||
this.trashNotes = notes || []
|
||||
} catch (err) {
|
||||
console.error('Failed to load trash notes:', err)
|
||||
this.$message.error('加载回收站失败')
|
||||
}
|
||||
},
|
||||
|
||||
async restoreNote(note) {
|
||||
try {
|
||||
await this.$confirm(`确定要恢复 "${note.title || '未命名'}" 吗?`, '提示', {
|
||||
type: 'info',
|
||||
confirmButtonText: '恢复',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
|
||||
await window.go.main.App.RestoreNote(note.id)
|
||||
await this.loadTrashNotes()
|
||||
await this.loadNoteTree()
|
||||
this.$message.success('已恢复')
|
||||
} catch (err) {
|
||||
if (err !== 'cancel') {
|
||||
console.error('Failed to restore note:', err)
|
||||
this.$message.error('恢复失败')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async permanentDeleteNote(note) {
|
||||
try {
|
||||
await this.$confirm(`确定要永久删除 "${note.title || '未命名'}" 吗?此操作不可恢复!`, '警告', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '永久删除',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
|
||||
await window.go.main.App.PermanentDeleteNote(note.id)
|
||||
await this.loadTrashNotes()
|
||||
this.$message.success('已永久删除')
|
||||
} catch (err) {
|
||||
if (err !== 'cancel') {
|
||||
console.error('Failed to permanently delete note:', err)
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async emptyTrash() {
|
||||
try {
|
||||
await this.$confirm('确定要清空回收站吗?所有笔记将被永久删除!', '警告', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '清空',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
|
||||
await window.go.main.App.EmptyTrash()
|
||||
await this.loadTrashNotes()
|
||||
this.$message.success('回收站已清空')
|
||||
} catch (err) {
|
||||
if (err !== 'cancel') {
|
||||
console.error('Failed to empty trash:', err)
|
||||
this.$message.error('清空失败')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 图片压缩为 JPEG 格式,质量 50
|
||||
compressImage(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => {
|
||||
const img = new Image()
|
||||
img.src = reader.result
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
ctx.drawImage(img, 0, 0)
|
||||
// 压缩为 JPEG 格式,质量 50
|
||||
const compressedDataUrl = canvas.toDataURL('image/jpeg', 0.5)
|
||||
resolve(compressedDataUrl)
|
||||
}
|
||||
img.onerror = reject
|
||||
}
|
||||
reader.onerror = reject
|
||||
})
|
||||
},
|
||||
|
||||
// mavon-editor 图片上传处理
|
||||
async handleImgAdd(pos, $file) {
|
||||
try {
|
||||
// 压缩图片
|
||||
const compressedDataUrl = await this.compressImage($file)
|
||||
// 保存到后端文件服务器
|
||||
const fileUrl = await window.go.main.App.SaveFile(compressedDataUrl, $file.name)
|
||||
// 替换编辑器中的图片链接
|
||||
this.$refs.md.$img2Url(pos, fileUrl)
|
||||
this.$message.success('图片上传成功')
|
||||
} catch (err) {
|
||||
console.error('Failed to upload image:', err)
|
||||
this.$message.error('图片上传失败')
|
||||
this.$refs.md.$img2Url(pos, '')
|
||||
}
|
||||
},
|
||||
|
||||
handleImgDel(pos) {
|
||||
// 图片删除时可以做清理工作(可选)
|
||||
console.log('Image deleted at position:', pos)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showTrash(newVal) {
|
||||
if (newVal) {
|
||||
this.loadTrashNotes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: #f5f7fa;
|
||||
border-right: 1px solid #e4e7ed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.user-info:hover {
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
margin-top: auto;
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 标签页样式 */
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabs-header {
|
||||
display: flex;
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
overflow-x: auto;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
height: 40px;
|
||||
border-right: 1px solid #e4e7ed;
|
||||
cursor: pointer;
|
||||
background-color: #f5f7fa;
|
||||
min-width: 120px;
|
||||
max-width: 200px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.tab-item.is-active {
|
||||
background-color: #fff;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tab-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
margin-left: 8px;
|
||||
opacity: 0;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-item:hover .tab-close {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab-close:hover {
|
||||
background-color: #dcdfe6;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.note-title-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.note-title-input .el-input__inner {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.note-title-input .el-input__inner:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 覆盖 mavon-editor 样式 */
|
||||
.v-note-wrapper {
|
||||
border: none !important;
|
||||
border-radius: 0 !important;
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
||||
.v-note-op {
|
||||
border-bottom: 1px solid #e4e7ed !important;
|
||||
}
|
||||
|
||||
.v-show-content {
|
||||
padding: 20px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 136 KiB |
@@ -0,0 +1,255 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h1 class="login-title">Kenaito Notebook</h1>
|
||||
<p class="login-subtitle">{{ isLogin ? '欢迎回来,请登录' : '创建新账号' }}</p>
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
@submit.native.prevent="handleSubmit"
|
||||
>
|
||||
<el-form-item prop="email">
|
||||
<el-input
|
||||
v-model="form.email"
|
||||
prefix-icon="el-icon-message"
|
||||
placeholder="请输入邮箱"
|
||||
size="medium"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
prefix-icon="el-icon-lock"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
size="medium"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!isLogin" prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="form.confirmPassword"
|
||||
prefix-icon="el-icon-lock"
|
||||
type="password"
|
||||
placeholder="请再次输入密码"
|
||||
size="medium"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!isLogin" prop="code">
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<el-input
|
||||
v-model="form.code"
|
||||
prefix-icon="el-icon-postcard"
|
||||
placeholder="请输入验证码"
|
||||
size="medium"
|
||||
style="flex: 1;"
|
||||
/>
|
||||
<el-button
|
||||
size="medium"
|
||||
:disabled="codeCooldown > 0 || !form.email"
|
||||
:loading="sendingCode"
|
||||
@click="handleSendCode"
|
||||
>
|
||||
{{ codeCooldown > 0 ? codeCooldown + 's' : '发送验证码' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
native-type="submit"
|
||||
:loading="loading"
|
||||
style="width: 100%"
|
||||
size="medium"
|
||||
>
|
||||
{{ loading ? '处理中...' : (isLogin ? '登 录' : '注 册') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="login-footer">
|
||||
<span>{{ isLogin ? '还没有账号?' : '已有账号?' }}</span>
|
||||
<el-button type="text" @click="toggleMode">
|
||||
{{ isLogin ? '立即注册' : '去登录' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SendVerifyCode } from '../../wailsjs/go/main/App'
|
||||
|
||||
export default {
|
||||
name: 'LoginView',
|
||||
data() {
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
if (value !== this.form.password) {
|
||||
callback(new Error('两次输入的密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
isLogin: true,
|
||||
loading: false,
|
||||
sendingCode: false,
|
||||
codeCooldown: 0,
|
||||
cooldownTimer: null,
|
||||
form: {
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
code: ''
|
||||
},
|
||||
rules: {
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码至少 6 个字符', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
{ len: 6, message: '验证码为 6 位数字', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.cooldownTimer) {
|
||||
clearInterval(this.cooldownTimer)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleMode() {
|
||||
this.isLogin = !this.isLogin
|
||||
if (this.cooldownTimer) {
|
||||
clearInterval(this.cooldownTimer)
|
||||
this.codeCooldown = 0
|
||||
}
|
||||
this.form.code = ''
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate()
|
||||
}
|
||||
})
|
||||
},
|
||||
async handleSendCode() {
|
||||
try {
|
||||
await this.$refs.form.validateField('email')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
this.sendingCode = true
|
||||
try {
|
||||
const purpose = 'register'
|
||||
await SendVerifyCode(this.form.email, purpose)
|
||||
this.$message.success('验证码已发送到您的邮箱')
|
||||
this.startCooldown()
|
||||
} catch (err) {
|
||||
this.$message.error(err.toString() || '发送验证码失败')
|
||||
} finally {
|
||||
this.sendingCode = false
|
||||
}
|
||||
},
|
||||
startCooldown() {
|
||||
this.codeCooldown = 60
|
||||
this.cooldownTimer = setInterval(() => {
|
||||
this.codeCooldown--
|
||||
if (this.codeCooldown <= 0) {
|
||||
clearInterval(this.cooldownTimer)
|
||||
this.cooldownTimer = null
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
async handleSubmit() {
|
||||
if (!this.$refs.form) return
|
||||
|
||||
try {
|
||||
await this.$refs.form.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
if (this.isLogin) {
|
||||
await window.go.main.App.Login(this.form.email, this.form.password)
|
||||
this.$message.success('登录成功')
|
||||
} else {
|
||||
await window.go.main.App.Register(this.form.email, this.form.password, this.form.code)
|
||||
this.$message.success('注册成功')
|
||||
}
|
||||
this.$emit('login-success')
|
||||
} catch (err) {
|
||||
this.$message.error(err.toString() || '操作失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 400px;
|
||||
padding: 40px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.el-form-item.is-required >>> .el-form-item__label::before {
|
||||
content: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,385 @@
|
||||
<template>
|
||||
<div class="note-tree" @contextmenu.prevent="showContextMenu($event, null)">
|
||||
<div v-if="!notes || notes.length === 0" class="empty-tree">
|
||||
<p>暂无笔记</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="tree-content">
|
||||
<div
|
||||
v-for="note in notes"
|
||||
:key="note.id"
|
||||
class="tree-item"
|
||||
>
|
||||
<div
|
||||
class="tree-node"
|
||||
:class="{
|
||||
'is-selected': selectedId === note.id,
|
||||
'is-folder': note.isFolder
|
||||
}"
|
||||
@click="$emit('select', note)"
|
||||
@contextmenu.prevent="showContextMenu($event, note)"
|
||||
>
|
||||
<span
|
||||
v-if="note.isFolder"
|
||||
class="expand-icon"
|
||||
:class="{ 'is-expanded': expandedIds.includes(note.id) }"
|
||||
@click.stop="toggleExpand(note.id)"
|
||||
>
|
||||
<i class="el-icon-arrow-right"></i>
|
||||
</span>
|
||||
<span v-else class="expand-icon-placeholder"></span>
|
||||
|
||||
<span class="node-icon">
|
||||
<i :class="getNodeIcon(note)"></i>
|
||||
</span>
|
||||
|
||||
<input
|
||||
v-if="renamingId === note.id"
|
||||
ref="renameInput"
|
||||
v-model="renameTitle"
|
||||
class="rename-input"
|
||||
@blur="confirmRename(note)"
|
||||
@keyup.enter="confirmRename(note)"
|
||||
@keyup.escape="cancelRename"
|
||||
@click.stop
|
||||
/>
|
||||
<span v-else class="node-label-wrapper">
|
||||
<span class="node-label" :title="note.title">
|
||||
{{ note.title || '未命名' }}
|
||||
</span>
|
||||
<span v-if="note.createdAt" class="node-date">
|
||||
{{ formatDate(note.createdAt) }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="note.isFolder && note.children && expandedIds.includes(note.id)"
|
||||
class="tree-children"
|
||||
>
|
||||
<note-tree
|
||||
:notes="note.children"
|
||||
:selected-id="selectedId"
|
||||
@select="$emit('select', $event)"
|
||||
@delete="$emit('delete', $event)"
|
||||
@context-menu="$emit('context-menu', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<div
|
||||
v-if="contextMenu.visible"
|
||||
class="context-menu"
|
||||
:style="{ top: contextMenu.y + 'px', left: contextMenu.x + 'px' }"
|
||||
@click.stop
|
||||
>
|
||||
<template v-if="contextMenu.note && contextMenu.note.isFolder">
|
||||
<div class="context-menu-item" @click.stop="handleContextAction('newChild')">
|
||||
<i class="el-icon-document-add"></i> 新建笔记
|
||||
</div>
|
||||
<div class="context-menu-item" @click.stop="handleContextAction('newFolder')">
|
||||
<i class="el-icon-folder-add"></i> 新建子文件夹
|
||||
</div>
|
||||
<div class="context-menu-divider"></div>
|
||||
<div class="context-menu-item" @click.stop="handleContextAction('rename')">
|
||||
<i class="el-icon-edit"></i> 重命名
|
||||
</div>
|
||||
<div class="context-menu-item" @click.stop="handleContextAction('exportFolder')">
|
||||
<i class="el-icon-download"></i> 导出为 Markdown
|
||||
</div>
|
||||
<div class="context-menu-item delete" @click.stop="handleContextAction('delete')">
|
||||
<i class="el-icon-delete"></i> 删除
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="contextMenu.note">
|
||||
<div class="context-menu-item delete" @click.stop="handleContextAction('delete')">
|
||||
<i class="el-icon-delete"></i> 删除
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="context-menu-item" @click.stop="handleContextAction('newNote')">
|
||||
<i class="el-icon-document-add"></i> 新建笔记
|
||||
</div>
|
||||
<div class="context-menu-item" @click.stop="handleContextAction('newFolder')">
|
||||
<i class="el-icon-folder-add"></i> 新建文件夹
|
||||
</div>
|
||||
<div class="context-menu-divider"></div>
|
||||
<div class="context-menu-item" @click.stop="handleContextAction('exportAll')">
|
||||
<i class="el-icon-download"></i> 导出所有笔记
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NoteTree',
|
||||
props: {
|
||||
notes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selectedId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expandedIds: [],
|
||||
renamingId: null,
|
||||
renameTitle: '',
|
||||
contextMenu: {
|
||||
visible: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
note: null
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.hideContextMenu)
|
||||
document.addEventListener('contextmenu', this.hideContextMenu)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('click', this.hideContextMenu)
|
||||
document.removeEventListener('contextmenu', this.hideContextMenu)
|
||||
},
|
||||
methods: {
|
||||
toggleExpand(id) {
|
||||
const index = this.expandedIds.indexOf(id)
|
||||
if (index > -1) {
|
||||
this.expandedIds.splice(index, 1)
|
||||
} else {
|
||||
this.expandedIds.push(id)
|
||||
}
|
||||
},
|
||||
|
||||
getNodeIcon(note) {
|
||||
if (note.isFolder) {
|
||||
return this.expandedIds.includes(note.id)
|
||||
? 'el-icon-folder-opened'
|
||||
: 'el-icon-folder'
|
||||
}
|
||||
return 'el-icon-document'
|
||||
},
|
||||
|
||||
showContextMenu(event, note) {
|
||||
event.stopPropagation()
|
||||
this.contextMenu = {
|
||||
visible: true,
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
note: note
|
||||
}
|
||||
},
|
||||
|
||||
hideContextMenu() {
|
||||
this.contextMenu.visible = false
|
||||
},
|
||||
|
||||
handleContextAction(command) {
|
||||
if (command === 'rename') {
|
||||
this.startRename(this.contextMenu.note)
|
||||
} else {
|
||||
this.$emit('context-menu', {
|
||||
command: command,
|
||||
note: this.contextMenu.note
|
||||
})
|
||||
}
|
||||
this.hideContextMenu()
|
||||
},
|
||||
|
||||
startRename(note) {
|
||||
this.renamingId = note.id
|
||||
this.renameTitle = note.title
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.renameInput) {
|
||||
this.$refs.renameInput.focus()
|
||||
this.$refs.renameInput.select()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async confirmRename(note) {
|
||||
if (!this.renamingId) return
|
||||
const newTitle = this.renameTitle.trim()
|
||||
if (newTitle && newTitle !== note.title) {
|
||||
await window.go.main.App.UpdateNote(note.id, newTitle, note.content, note.tag)
|
||||
this.$emit('context-menu', { command: 'renamed', note })
|
||||
}
|
||||
this.renamingId = null
|
||||
this.renameTitle = ''
|
||||
},
|
||||
|
||||
cancelRename() {
|
||||
this.renamingId = null
|
||||
this.renameTitle = ''
|
||||
},
|
||||
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
notes: {
|
||||
handler(newNotes) {
|
||||
// 自动展开第一级文件夹
|
||||
if (newNotes) {
|
||||
newNotes.forEach(note => {
|
||||
if (note.isFolder && note.children && note.children.length > 0) {
|
||||
if (!this.expandedIds.includes(note.id)) {
|
||||
this.expandedIds.push(note.id)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.note-tree {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.empty-tree {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.tree-content {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.tree-item {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree-node:hover {
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.tree-node.is-selected {
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.tree-node.is-folder {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.expand-icon.is-expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.expand-icon-placeholder {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.node-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.node-label-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.node-date {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.rename-input {
|
||||
flex: 1;
|
||||
border: 1px solid #409eff;
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.tree-children {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
/* 右键菜单 */
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
background: white;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
min-width: 160px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.context-menu-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.context-menu-item.delete {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.context-menu-divider {
|
||||
height: 1px;
|
||||
background-color: #e4e7ed;
|
||||
margin: 4px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div class="trash-view">
|
||||
<div class="trash-header">
|
||||
<h3>
|
||||
<i class="el-icon-delete"></i> 回收站
|
||||
<span v-if="trashNotes.length > 0" class="trash-count">({{ trashNotes.length }})</span>
|
||||
</h3>
|
||||
<div class="trash-header-actions">
|
||||
<el-button
|
||||
v-if="trashNotes.length > 0"
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="el-icon-delete-solid"
|
||||
@click="emptyTrash"
|
||||
>
|
||||
清空回收站
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="trashNotes.length === 0" class="empty-trash">
|
||||
<i class="el-icon-folder-delete"></i>
|
||||
<p>回收站为空</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="trash-list">
|
||||
<div
|
||||
v-for="note in trashNotes"
|
||||
:key="note.id"
|
||||
class="trash-item"
|
||||
>
|
||||
<div class="trash-item-icon">
|
||||
<i :class="note.isFolder ? 'el-icon-folder' : 'el-icon-document'"></i>
|
||||
</div>
|
||||
<div class="trash-item-info">
|
||||
<div class="trash-item-title">{{ note.title || '未命名' }}</div>
|
||||
<div class="trash-item-meta">
|
||||
<span v-if="note.deletedAt" class="delete-time">
|
||||
删除于: {{ formatDate(note.deletedAt) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trash-item-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
icon="el-icon-refresh"
|
||||
@click="restoreNote(note)"
|
||||
>
|
||||
恢复
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
@click="permanentDelete(note)"
|
||||
>
|
||||
永久删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TrashView',
|
||||
props: {
|
||||
trashNotes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||
},
|
||||
|
||||
restoreNote(note) {
|
||||
this.$emit('restore', note)
|
||||
},
|
||||
|
||||
permanentDelete(note) {
|
||||
this.$emit('permanent-delete', note)
|
||||
},
|
||||
|
||||
emptyTrash() {
|
||||
this.$emit('empty-trash')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trash-view {
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.trash-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.trash-header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.trash-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.trash-count {
|
||||
color: #909399;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.empty-trash {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-trash i {
|
||||
font-size: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.trash-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.trash-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.trash-item:hover {
|
||||
border-color: #dcdfe6;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.trash-item-icon {
|
||||
font-size: 24px;
|
||||
color: #909399;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.trash-item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.trash-item-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.trash-item-meta {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.trash-item-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,14 @@
|
||||
import Vue from 'vue'
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import mavonEditor from 'mavon-editor'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
import App from './App.vue'
|
||||
import './style.css'
|
||||
|
||||
Vue.use(ElementUI, { size: 'small' })
|
||||
Vue.use(mavonEditor)
|
||||
|
||||
new Vue({
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
@@ -0,0 +1,160 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #ffffff;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* 全局滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #c0c4cc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Element UI 样式覆盖 */
|
||||
.el-menu {
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
.el-tree {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Markdown 预览样式 */
|
||||
.markdown-body {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
background-color: rgba(27, 31, 35, 0.05);
|
||||
border-radius: 3px;
|
||||
font-size: 85%;
|
||||
margin: 0;
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 3px;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.markdown-body pre code {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
display: inline;
|
||||
line-height: inherit;
|
||||
margin: 0;
|
||||
max-width: auto;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
white-space: pre;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
border-left: 0.25em solid #dfe2e5;
|
||||
color: #6a737d;
|
||||
margin: 0;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
display: block;
|
||||
width: max-content;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.markdown-body table th,
|
||||
.markdown-body table td {
|
||||
border: 1px solid #dfe2e5;
|
||||
padding: 6px 13px;
|
||||
}
|
||||
|
||||
.markdown-body table th {
|
||||
font-weight: 600;
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.markdown-body table tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #c6cbd1;
|
||||
}
|
||||
|
||||
.markdown-body table tr:nth-child(2n) {
|
||||
background-color: #f6f8fa;
|
||||
}
|
||||
|
||||
.markdown-body img {
|
||||
border-style: none;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
color: #0366d6;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown-body hr {
|
||||
height: 0.25em;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: #e1e4e8;
|
||||
border: 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {defineConfig} from 'vite'
|
||||
import { createVuePlugin } from 'vite-plugin-vue2'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [createVuePlugin()],
|
||||
server: {
|
||||
// Wails dev 模式下的配置
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
// 不启用 HMR 的 WebSocket 代理,让 Wails 处理
|
||||
},
|
||||
// 确保资源路径正确
|
||||
base: './',
|
||||
build: {
|
||||
// 确保输出目录正确
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets'
|
||||
}
|
||||
})
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {models} from '../models';
|
||||
|
||||
export function CreateNote(arg1:number,arg2:string,arg3:boolean):Promise<models.Note>;
|
||||
|
||||
export function DeleteNote(arg1:number):Promise<void>;
|
||||
|
||||
export function EmptyTrash():Promise<void>;
|
||||
|
||||
export function ExportAllMarkdown():Promise<string>;
|
||||
|
||||
export function ExportFolderMarkdown(arg1:number):Promise<string>;
|
||||
|
||||
export function ExportHTML(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function ExportMarkdown(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function GetAssetsPath():Promise<string>;
|
||||
|
||||
export function GetFileServerURL():Promise<string>;
|
||||
|
||||
export function GetNote(arg1:number):Promise<models.Note>;
|
||||
|
||||
export function GetNoteTree():Promise<Array<models.NoteTree>>;
|
||||
|
||||
export function GetTrashNotes():Promise<Array<models.Note>>;
|
||||
|
||||
export function GetUser():Promise<Record<string, any>>;
|
||||
|
||||
export function IsLoggedIn():Promise<boolean>;
|
||||
|
||||
export function Login(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function Logout():Promise<void>;
|
||||
|
||||
export function PermanentDeleteNote(arg1:number):Promise<void>;
|
||||
|
||||
export function Register(arg1:string,arg2:string,arg3:string):Promise<string>;
|
||||
|
||||
export function ResetPassword(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
|
||||
export function RestoreNote(arg1:number):Promise<void>;
|
||||
|
||||
export function RevealInFinder(arg1:string):Promise<void>;
|
||||
|
||||
export function SaveFile(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SaveImage(arg1:string):Promise<string>;
|
||||
|
||||
export function SearchNotes(arg1:string):Promise<Array<models.Note>>;
|
||||
|
||||
export function SelectDirectory(arg1:string):Promise<string>;
|
||||
|
||||
export function SelectSaveFile(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SendVerifyCode(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function UpdateNote(arg1:number,arg2:string,arg3:string,arg4:string):Promise<void>;
|
||||
|
||||
export function VerifyCode(arg1:string,arg2:string,arg3:string):Promise<void>;
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function CreateNote(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['CreateNote'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function DeleteNote(arg1) {
|
||||
return window['go']['main']['App']['DeleteNote'](arg1);
|
||||
}
|
||||
|
||||
export function EmptyTrash() {
|
||||
return window['go']['main']['App']['EmptyTrash']();
|
||||
}
|
||||
|
||||
export function ExportAllMarkdown() {
|
||||
return window['go']['main']['App']['ExportAllMarkdown']();
|
||||
}
|
||||
|
||||
export function ExportFolderMarkdown(arg1) {
|
||||
return window['go']['main']['App']['ExportFolderMarkdown'](arg1);
|
||||
}
|
||||
|
||||
export function ExportHTML(arg1, arg2) {
|
||||
return window['go']['main']['App']['ExportHTML'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function ExportMarkdown(arg1, arg2) {
|
||||
return window['go']['main']['App']['ExportMarkdown'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function GetAssetsPath() {
|
||||
return window['go']['main']['App']['GetAssetsPath']();
|
||||
}
|
||||
|
||||
export function GetFileServerURL() {
|
||||
return window['go']['main']['App']['GetFileServerURL']();
|
||||
}
|
||||
|
||||
export function GetNote(arg1) {
|
||||
return window['go']['main']['App']['GetNote'](arg1);
|
||||
}
|
||||
|
||||
export function GetNoteTree() {
|
||||
return window['go']['main']['App']['GetNoteTree']();
|
||||
}
|
||||
|
||||
export function GetTrashNotes() {
|
||||
return window['go']['main']['App']['GetTrashNotes']();
|
||||
}
|
||||
|
||||
export function GetUser() {
|
||||
return window['go']['main']['App']['GetUser']();
|
||||
}
|
||||
|
||||
export function IsLoggedIn() {
|
||||
return window['go']['main']['App']['IsLoggedIn']();
|
||||
}
|
||||
|
||||
export function Login(arg1, arg2) {
|
||||
return window['go']['main']['App']['Login'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function Logout() {
|
||||
return window['go']['main']['App']['Logout']();
|
||||
}
|
||||
|
||||
export function PermanentDeleteNote(arg1) {
|
||||
return window['go']['main']['App']['PermanentDeleteNote'](arg1);
|
||||
}
|
||||
|
||||
export function Register(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['Register'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function ResetPassword(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['ResetPassword'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function RestoreNote(arg1) {
|
||||
return window['go']['main']['App']['RestoreNote'](arg1);
|
||||
}
|
||||
|
||||
export function RevealInFinder(arg1) {
|
||||
return window['go']['main']['App']['RevealInFinder'](arg1);
|
||||
}
|
||||
|
||||
export function SaveFile(arg1, arg2) {
|
||||
return window['go']['main']['App']['SaveFile'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SaveImage(arg1) {
|
||||
return window['go']['main']['App']['SaveImage'](arg1);
|
||||
}
|
||||
|
||||
export function SearchNotes(arg1) {
|
||||
return window['go']['main']['App']['SearchNotes'](arg1);
|
||||
}
|
||||
|
||||
export function SelectDirectory(arg1) {
|
||||
return window['go']['main']['App']['SelectDirectory'](arg1);
|
||||
}
|
||||
|
||||
export function SelectSaveFile(arg1, arg2) {
|
||||
return window['go']['main']['App']['SelectSaveFile'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SendVerifyCode(arg1, arg2) {
|
||||
return window['go']['main']['App']['SendVerifyCode'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function UpdateNote(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['main']['App']['UpdateNote'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function VerifyCode(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['VerifyCode'](arg1, arg2, arg3);
|
||||
}
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
export namespace models {
|
||||
|
||||
export class Note {
|
||||
id: number;
|
||||
parentId: number;
|
||||
title: string;
|
||||
content: string;
|
||||
sortOrder: number;
|
||||
isFolder: boolean;
|
||||
tag: string;
|
||||
// Go type: time
|
||||
createdAt: any;
|
||||
// Go type: time
|
||||
updatedAt: any;
|
||||
// Go type: time
|
||||
deletedAt?: any;
|
||||
children?: Note[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Note(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.parentId = source["parentId"];
|
||||
this.title = source["title"];
|
||||
this.content = source["content"];
|
||||
this.sortOrder = source["sortOrder"];
|
||||
this.isFolder = source["isFolder"];
|
||||
this.tag = source["tag"];
|
||||
this.createdAt = this.convertValues(source["createdAt"], null);
|
||||
this.updatedAt = this.convertValues(source["updatedAt"], null);
|
||||
this.deletedAt = this.convertValues(source["deletedAt"], null);
|
||||
this.children = this.convertValues(source["children"], Note);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class NoteTree {
|
||||
id: number;
|
||||
parentId: number;
|
||||
title: string;
|
||||
content: string;
|
||||
sortOrder: number;
|
||||
isFolder: boolean;
|
||||
tag: string;
|
||||
// Go type: time
|
||||
createdAt: any;
|
||||
// Go type: time
|
||||
updatedAt: any;
|
||||
// Go type: time
|
||||
deletedAt?: any;
|
||||
children?: Note[];
|
||||
children: NoteTree[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new NoteTree(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.parentId = source["parentId"];
|
||||
this.title = source["title"];
|
||||
this.content = source["content"];
|
||||
this.sortOrder = source["sortOrder"];
|
||||
this.isFolder = source["isFolder"];
|
||||
this.tag = source["tag"];
|
||||
this.createdAt = this.convertValues(source["createdAt"], null);
|
||||
this.updatedAt = this.convertValues(source["updatedAt"], null);
|
||||
this.deletedAt = this.convertValues(source["deletedAt"], null);
|
||||
this.children = this.convertValues(source["children"], Note);
|
||||
this.children = this.convertValues(source["children"], NoteTree);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@wailsapp/runtime",
|
||||
"version": "2.0.0",
|
||||
"description": "Wails Javascript runtime library",
|
||||
"main": "runtime.js",
|
||||
"types": "runtime.d.ts",
|
||||
"scripts": {
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wailsapp/wails.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Wails",
|
||||
"Javascript",
|
||||
"Go"
|
||||
],
|
||||
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wailsapp/wails/issues"
|
||||
},
|
||||
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export interface Screen {
|
||||
isCurrent: boolean;
|
||||
isPrimary: boolean;
|
||||
width : number
|
||||
height : number
|
||||
}
|
||||
|
||||
// Environment information such as platform, buildtype, ...
|
||||
export interface EnvironmentInfo {
|
||||
buildType: string;
|
||||
platform: string;
|
||||
arch: string;
|
||||
}
|
||||
|
||||
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
|
||||
// emits the given event. Optional data may be passed with the event.
|
||||
// This will trigger any event listeners.
|
||||
export function EventsEmit(eventName: string, ...data: any): void;
|
||||
|
||||
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
|
||||
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
|
||||
// sets up a listener for the given event name, but will only trigger a given number times.
|
||||
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
|
||||
|
||||
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
|
||||
// sets up a listener for the given event name, but will only trigger once.
|
||||
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
|
||||
// unregisters the listener for the given event name.
|
||||
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
|
||||
|
||||
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
|
||||
// unregisters all listeners.
|
||||
export function EventsOffAll(): void;
|
||||
|
||||
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
|
||||
// logs the given message as a raw message
|
||||
export function LogPrint(message: string): void;
|
||||
|
||||
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
|
||||
// logs the given message at the `trace` log level.
|
||||
export function LogTrace(message: string): void;
|
||||
|
||||
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
|
||||
// logs the given message at the `debug` log level.
|
||||
export function LogDebug(message: string): void;
|
||||
|
||||
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
|
||||
// logs the given message at the `error` log level.
|
||||
export function LogError(message: string): void;
|
||||
|
||||
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
|
||||
// logs the given message at the `fatal` log level.
|
||||
// The application will quit after calling this method.
|
||||
export function LogFatal(message: string): void;
|
||||
|
||||
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
|
||||
// logs the given message at the `info` log level.
|
||||
export function LogInfo(message: string): void;
|
||||
|
||||
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
|
||||
// logs the given message at the `warning` log level.
|
||||
export function LogWarning(message: string): void;
|
||||
|
||||
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
|
||||
// Forces a reload by the main application as well as connected browsers.
|
||||
export function WindowReload(): void;
|
||||
|
||||
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
|
||||
// Reloads the application frontend.
|
||||
export function WindowReloadApp(): void;
|
||||
|
||||
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
|
||||
// Sets the window AlwaysOnTop or not on top.
|
||||
export function WindowSetAlwaysOnTop(b: boolean): void;
|
||||
|
||||
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
|
||||
// *Windows only*
|
||||
// Sets window theme to system default (dark/light).
|
||||
export function WindowSetSystemDefaultTheme(): void;
|
||||
|
||||
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
|
||||
// *Windows only*
|
||||
// Sets window to light theme.
|
||||
export function WindowSetLightTheme(): void;
|
||||
|
||||
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
|
||||
// *Windows only*
|
||||
// Sets window to dark theme.
|
||||
export function WindowSetDarkTheme(): void;
|
||||
|
||||
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
|
||||
// Centers the window on the monitor the window is currently on.
|
||||
export function WindowCenter(): void;
|
||||
|
||||
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
|
||||
// Sets the text in the window title bar.
|
||||
export function WindowSetTitle(title: string): void;
|
||||
|
||||
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
|
||||
// Makes the window full screen.
|
||||
export function WindowFullscreen(): void;
|
||||
|
||||
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
|
||||
// Restores the previous window dimensions and position prior to full screen.
|
||||
export function WindowUnfullscreen(): void;
|
||||
|
||||
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
|
||||
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||
export function WindowIsFullscreen(): Promise<boolean>;
|
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): void;
|
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
export function WindowGetSize(): Promise<Size>;
|
||||
|
||||
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
|
||||
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMaxSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
|
||||
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMinSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
|
||||
// Sets the window position relative to the monitor the window is currently on.
|
||||
export function WindowSetPosition(x: number, y: number): void;
|
||||
|
||||
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
|
||||
// Gets the window position relative to the monitor the window is currently on.
|
||||
export function WindowGetPosition(): Promise<Position>;
|
||||
|
||||
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
|
||||
// Hides the window.
|
||||
export function WindowHide(): void;
|
||||
|
||||
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
|
||||
// Shows the window, if it is currently hidden.
|
||||
export function WindowShow(): void;
|
||||
|
||||
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
|
||||
// Maximises the window to fill the screen.
|
||||
export function WindowMaximise(): void;
|
||||
|
||||
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
|
||||
// Toggles between Maximised and UnMaximised.
|
||||
export function WindowToggleMaximise(): void;
|
||||
|
||||
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
|
||||
// Restores the window to the dimensions and position prior to maximising.
|
||||
export function WindowUnmaximise(): void;
|
||||
|
||||
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
|
||||
// Returns the state of the window, i.e. whether the window is maximised or not.
|
||||
export function WindowIsMaximised(): Promise<boolean>;
|
||||
|
||||
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
|
||||
// Minimises the window.
|
||||
export function WindowMinimise(): void;
|
||||
|
||||
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
|
||||
// Restores the window to the dimensions and position prior to minimising.
|
||||
export function WindowUnminimise(): void;
|
||||
|
||||
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
|
||||
// Returns the state of the window, i.e. whether the window is minimised or not.
|
||||
export function WindowIsMinimised(): Promise<boolean>;
|
||||
|
||||
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
|
||||
// Returns the state of the window, i.e. whether the window is normal or not.
|
||||
export function WindowIsNormal(): Promise<boolean>;
|
||||
|
||||
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
|
||||
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
|
||||
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
|
||||
|
||||
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
|
||||
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||
export function ScreenGetAll(): Promise<Screen[]>;
|
||||
|
||||
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
|
||||
// Opens the given URL in the system browser.
|
||||
export function BrowserOpenURL(url: string): void;
|
||||
|
||||
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
|
||||
// Returns information about the environment
|
||||
export function Environment(): Promise<EnvironmentInfo>;
|
||||
|
||||
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
|
||||
// Quits the application.
|
||||
export function Quit(): void;
|
||||
|
||||
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
|
||||
// Hides the application.
|
||||
export function Hide(): void;
|
||||
|
||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
|
||||
// Shows the application.
|
||||
export function Show(): void;
|
||||
|
||||
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
|
||||
// Returns the current text stored on clipboard
|
||||
export function ClipboardGetText(): Promise<string>;
|
||||
|
||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||
// Sets a text on the clipboard
|
||||
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||
|
||||
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
|
||||
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
|
||||
|
||||
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
|
||||
// OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
export function OnFileDropOff() :void
|
||||
|
||||
// Check if the file path resolver is available
|
||||
export function CanResolveFilePaths(): boolean;
|
||||
|
||||
// Resolves file paths for an array of files
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
|
||||
// Notification types
|
||||
export interface NotificationOptions {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string; // macOS and Linux only
|
||||
body?: string;
|
||||
categoryId?: string;
|
||||
data?: { [key: string]: any };
|
||||
}
|
||||
|
||||
export interface NotificationAction {
|
||||
id?: string;
|
||||
title?: string;
|
||||
destructive?: boolean; // macOS-specific
|
||||
}
|
||||
|
||||
export interface NotificationCategory {
|
||||
id?: string;
|
||||
actions?: NotificationAction[];
|
||||
hasReplyField?: boolean;
|
||||
replyPlaceholder?: string;
|
||||
replyButtonTitle?: string;
|
||||
}
|
||||
|
||||
// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
|
||||
// Initializes the notification service for the application.
|
||||
// This must be called before sending any notifications.
|
||||
export function InitializeNotifications(): Promise<void>;
|
||||
|
||||
// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
|
||||
// Cleans up notification resources and releases any held connections.
|
||||
export function CleanupNotifications(): Promise<void>;
|
||||
|
||||
// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
|
||||
// Checks if notifications are available on the current platform.
|
||||
export function IsNotificationAvailable(): Promise<boolean>;
|
||||
|
||||
// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
|
||||
// Requests notification authorization from the user (macOS only).
|
||||
export function RequestNotificationAuthorization(): Promise<boolean>;
|
||||
|
||||
// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
|
||||
// Checks the current notification authorization status (macOS only).
|
||||
export function CheckNotificationAuthorization(): Promise<boolean>;
|
||||
|
||||
// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
|
||||
// Sends a basic notification with the given options.
|
||||
export function SendNotification(options: NotificationOptions): Promise<void>;
|
||||
|
||||
// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
|
||||
// Sends a notification with action buttons. Requires a registered category.
|
||||
export function SendNotificationWithActions(options: NotificationOptions): Promise<void>;
|
||||
|
||||
// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
|
||||
// Registers a notification category that can be used with SendNotificationWithActions.
|
||||
export function RegisterNotificationCategory(category: NotificationCategory): Promise<void>;
|
||||
|
||||
// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
|
||||
// Removes a previously registered notification category.
|
||||
export function RemoveNotificationCategory(categoryId: string): Promise<void>;
|
||||
|
||||
// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
|
||||
// Removes all pending notifications from the notification center.
|
||||
export function RemoveAllPendingNotifications(): Promise<void>;
|
||||
|
||||
// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
|
||||
// Removes a specific pending notification by its identifier.
|
||||
export function RemovePendingNotification(identifier: string): Promise<void>;
|
||||
|
||||
// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
|
||||
// Removes all delivered notifications from the notification center.
|
||||
export function RemoveAllDeliveredNotifications(): Promise<void>;
|
||||
|
||||
// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
|
||||
// Removes a specific delivered notification by its identifier.
|
||||
export function RemoveDeliveredNotification(identifier: string): Promise<void>;
|
||||
|
||||
// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
|
||||
// Removes a notification by its identifier (cross-platform convenience function).
|
||||
export function RemoveNotification(identifier: string): Promise<void>;
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export function LogPrint(message) {
|
||||
window.runtime.LogPrint(message);
|
||||
}
|
||||
|
||||
export function LogTrace(message) {
|
||||
window.runtime.LogTrace(message);
|
||||
}
|
||||
|
||||
export function LogDebug(message) {
|
||||
window.runtime.LogDebug(message);
|
||||
}
|
||||
|
||||
export function LogInfo(message) {
|
||||
window.runtime.LogInfo(message);
|
||||
}
|
||||
|
||||
export function LogWarning(message) {
|
||||
window.runtime.LogWarning(message);
|
||||
}
|
||||
|
||||
export function LogError(message) {
|
||||
window.runtime.LogError(message);
|
||||
}
|
||||
|
||||
export function LogFatal(message) {
|
||||
window.runtime.LogFatal(message);
|
||||
}
|
||||
|
||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
|
||||
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
|
||||
}
|
||||
|
||||
export function EventsOn(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, -1);
|
||||
}
|
||||
|
||||
export function EventsOff(eventName, ...additionalEventNames) {
|
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||
}
|
||||
|
||||
export function EventsOffAll() {
|
||||
return window.runtime.EventsOffAll();
|
||||
}
|
||||
|
||||
export function EventsOnce(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
export function EventsEmit(eventName) {
|
||||
let args = [eventName].slice.call(arguments);
|
||||
return window.runtime.EventsEmit.apply(null, args);
|
||||
}
|
||||
|
||||
export function WindowReload() {
|
||||
window.runtime.WindowReload();
|
||||
}
|
||||
|
||||
export function WindowReloadApp() {
|
||||
window.runtime.WindowReloadApp();
|
||||
}
|
||||
|
||||
export function WindowSetAlwaysOnTop(b) {
|
||||
window.runtime.WindowSetAlwaysOnTop(b);
|
||||
}
|
||||
|
||||
export function WindowSetSystemDefaultTheme() {
|
||||
window.runtime.WindowSetSystemDefaultTheme();
|
||||
}
|
||||
|
||||
export function WindowSetLightTheme() {
|
||||
window.runtime.WindowSetLightTheme();
|
||||
}
|
||||
|
||||
export function WindowSetDarkTheme() {
|
||||
window.runtime.WindowSetDarkTheme();
|
||||
}
|
||||
|
||||
export function WindowCenter() {
|
||||
window.runtime.WindowCenter();
|
||||
}
|
||||
|
||||
export function WindowSetTitle(title) {
|
||||
window.runtime.WindowSetTitle(title);
|
||||
}
|
||||
|
||||
export function WindowFullscreen() {
|
||||
window.runtime.WindowFullscreen();
|
||||
}
|
||||
|
||||
export function WindowUnfullscreen() {
|
||||
window.runtime.WindowUnfullscreen();
|
||||
}
|
||||
|
||||
export function WindowIsFullscreen() {
|
||||
return window.runtime.WindowIsFullscreen();
|
||||
}
|
||||
|
||||
export function WindowGetSize() {
|
||||
return window.runtime.WindowGetSize();
|
||||
}
|
||||
|
||||
export function WindowSetSize(width, height) {
|
||||
window.runtime.WindowSetSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMaxSize(width, height) {
|
||||
window.runtime.WindowSetMaxSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMinSize(width, height) {
|
||||
window.runtime.WindowSetMinSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetPosition(x, y) {
|
||||
window.runtime.WindowSetPosition(x, y);
|
||||
}
|
||||
|
||||
export function WindowGetPosition() {
|
||||
return window.runtime.WindowGetPosition();
|
||||
}
|
||||
|
||||
export function WindowHide() {
|
||||
window.runtime.WindowHide();
|
||||
}
|
||||
|
||||
export function WindowShow() {
|
||||
window.runtime.WindowShow();
|
||||
}
|
||||
|
||||
export function WindowMaximise() {
|
||||
window.runtime.WindowMaximise();
|
||||
}
|
||||
|
||||
export function WindowToggleMaximise() {
|
||||
window.runtime.WindowToggleMaximise();
|
||||
}
|
||||
|
||||
export function WindowUnmaximise() {
|
||||
window.runtime.WindowUnmaximise();
|
||||
}
|
||||
|
||||
export function WindowIsMaximised() {
|
||||
return window.runtime.WindowIsMaximised();
|
||||
}
|
||||
|
||||
export function WindowMinimise() {
|
||||
window.runtime.WindowMinimise();
|
||||
}
|
||||
|
||||
export function WindowUnminimise() {
|
||||
window.runtime.WindowUnminimise();
|
||||
}
|
||||
|
||||
export function WindowSetBackgroundColour(R, G, B, A) {
|
||||
window.runtime.WindowSetBackgroundColour(R, G, B, A);
|
||||
}
|
||||
|
||||
export function ScreenGetAll() {
|
||||
return window.runtime.ScreenGetAll();
|
||||
}
|
||||
|
||||
export function WindowIsMinimised() {
|
||||
return window.runtime.WindowIsMinimised();
|
||||
}
|
||||
|
||||
export function WindowIsNormal() {
|
||||
return window.runtime.WindowIsNormal();
|
||||
}
|
||||
|
||||
export function BrowserOpenURL(url) {
|
||||
window.runtime.BrowserOpenURL(url);
|
||||
}
|
||||
|
||||
export function Environment() {
|
||||
return window.runtime.Environment();
|
||||
}
|
||||
|
||||
export function Quit() {
|
||||
window.runtime.Quit();
|
||||
}
|
||||
|
||||
export function Hide() {
|
||||
window.runtime.Hide();
|
||||
}
|
||||
|
||||
export function Show() {
|
||||
window.runtime.Show();
|
||||
}
|
||||
|
||||
export function ClipboardGetText() {
|
||||
return window.runtime.ClipboardGetText();
|
||||
}
|
||||
|
||||
export function ClipboardSetText(text) {
|
||||
return window.runtime.ClipboardSetText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
*
|
||||
* @export
|
||||
* @callback OnFileDropCallback
|
||||
* @param {number} x - x coordinate of the drop
|
||||
* @param {number} y - y coordinate of the drop
|
||||
* @param {string[]} paths - A list of file paths.
|
||||
*/
|
||||
|
||||
/**
|
||||
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
*
|
||||
* @export
|
||||
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
|
||||
*/
|
||||
export function OnFileDrop(callback, useDropTarget) {
|
||||
return window.runtime.OnFileDrop(callback, useDropTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
*/
|
||||
export function OnFileDropOff() {
|
||||
return window.runtime.OnFileDropOff();
|
||||
}
|
||||
|
||||
export function CanResolveFilePaths() {
|
||||
return window.runtime.CanResolveFilePaths();
|
||||
}
|
||||
|
||||
export function ResolveFilePaths(files) {
|
||||
return window.runtime.ResolveFilePaths(files);
|
||||
}
|
||||
|
||||
export function InitializeNotifications() {
|
||||
return window.runtime.InitializeNotifications();
|
||||
}
|
||||
|
||||
export function CleanupNotifications() {
|
||||
return window.runtime.CleanupNotifications();
|
||||
}
|
||||
|
||||
export function IsNotificationAvailable() {
|
||||
return window.runtime.IsNotificationAvailable();
|
||||
}
|
||||
|
||||
export function RequestNotificationAuthorization() {
|
||||
return window.runtime.RequestNotificationAuthorization();
|
||||
}
|
||||
|
||||
export function CheckNotificationAuthorization() {
|
||||
return window.runtime.CheckNotificationAuthorization();
|
||||
}
|
||||
|
||||
export function SendNotification(options) {
|
||||
return window.runtime.SendNotification(options);
|
||||
}
|
||||
|
||||
export function SendNotificationWithActions(options) {
|
||||
return window.runtime.SendNotificationWithActions(options);
|
||||
}
|
||||
|
||||
export function RegisterNotificationCategory(category) {
|
||||
return window.runtime.RegisterNotificationCategory(category);
|
||||
}
|
||||
|
||||
export function RemoveNotificationCategory(categoryId) {
|
||||
return window.runtime.RemoveNotificationCategory(categoryId);
|
||||
}
|
||||
|
||||
export function RemoveAllPendingNotifications() {
|
||||
return window.runtime.RemoveAllPendingNotifications();
|
||||
}
|
||||
|
||||
export function RemovePendingNotification(identifier) {
|
||||
return window.runtime.RemovePendingNotification(identifier);
|
||||
}
|
||||
|
||||
export function RemoveAllDeliveredNotifications() {
|
||||
return window.runtime.RemoveAllDeliveredNotifications();
|
||||
}
|
||||
|
||||
export function RemoveDeliveredNotification(identifier) {
|
||||
return window.runtime.RemoveDeliveredNotification(identifier);
|
||||
}
|
||||
|
||||
export function RemoveNotification(identifier) {
|
||||
return window.runtime.RemoveNotification(identifier);
|
||||
}
|
||||
Reference in New Issue
Block a user