You've already forked SmartisanNote.Remake
开始完善便签新建、编辑逻辑
This commit is contained in:
10
index.html
10
index.html
@@ -4,7 +4,10 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
|
<!-- 透明 -->
|
||||||
|
<meta name="apple-mobile-web-app-orientation" content="portrait" />
|
||||||
|
<!-- 纵向 -->
|
||||||
<title>锤子便签</title>
|
<title>锤子便签</title>
|
||||||
<style>
|
<style>
|
||||||
/* Smartisan Notes Color Scheme - Based on Original Design */
|
/* Smartisan Notes Color Scheme - Based on Original Design */
|
||||||
@@ -15,8 +18,8 @@
|
|||||||
--primary-light: #f5f0e6; /* Light background tone */
|
--primary-light: #f5f0e6; /* Light background tone */
|
||||||
|
|
||||||
/* Editor typography - Consistent font size and line height */
|
/* Editor typography - Consistent font size and line height */
|
||||||
--editor-font-size: 19px; /* Base font size for editor */
|
--editor-font-size: 23px; /* Base font size for editor */
|
||||||
--editor-line-height: 1.5; /* Line height for editor */
|
--editor-line-height: 1.1; /* Line height for editor */
|
||||||
|
|
||||||
/* Background colors - Warm paper-like tones */
|
/* Background colors - Warm paper-like tones */
|
||||||
--background: #fbf7ed; /* Main app background - warm off-white */
|
--background: #fbf7ed; /* Main app background - warm off-white */
|
||||||
@@ -92,7 +95,6 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
background-image: repeating-linear-gradient(0deg, transparent, transparent 10px, var(--background-secondary) 10px, var(--background-secondary) 20px);
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
/* 适配iPhone X及更新机型的刘海屏 */
|
/* 适配iPhone X及更新机型的刘海屏 */
|
||||||
padding-top: env(safe-area-inset-top);
|
padding-top: env(safe-area-inset-top);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ html {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="code-fun-flex-row code-fun-justify-between mt-17-5">
|
<div class="code-fun-flex-row code-fun-justify-between mt-17-5">
|
||||||
<!-- 便签正文第一行 -->
|
<!-- 便签正文第一行 -->
|
||||||
<span class="font_3 text_19">{{ title }}</span>
|
<span class="font_3 text_19">{{ content }}</span>
|
||||||
<!-- 便签中是否存在图片 -->
|
<!-- 便签中是否存在图片 -->
|
||||||
<img v-if="hasImage" class="image_28" src="/assets/icons/drawable-xxhdpi/list_item_image_icon.png" />
|
<img v-if="hasImage" class="image_28" src="/assets/icons/drawable-xxhdpi/list_item_image_icon.png" />
|
||||||
</div>
|
</div>
|
||||||
@@ -32,10 +32,6 @@
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
content: {
|
content: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -251,6 +247,12 @@ const handleTouchEnd = () => {
|
|||||||
color: #816d61;
|
color: #816d61;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
line-height: 0.9rem;
|
line-height: 0.9rem;
|
||||||
|
word-break: break-all;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
}
|
}
|
||||||
.image_28 {
|
.image_28 {
|
||||||
width: 1.06rem;
|
width: 1.06rem;
|
||||||
|
|||||||
@@ -28,6 +28,26 @@ const editorRef = ref(null)
|
|||||||
const content = ref(props.modelValue || '')
|
const content = ref(props.modelValue || '')
|
||||||
const isToolbarVisible = ref(false)
|
const isToolbarVisible = ref(false)
|
||||||
|
|
||||||
|
// 初始化编辑器内容
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('RichTextEditor mounted, initial content:', props.modelValue)
|
||||||
|
if (editorRef.value) {
|
||||||
|
if (props.modelValue) {
|
||||||
|
try {
|
||||||
|
editorRef.value.innerHTML = props.modelValue
|
||||||
|
content.value = props.modelValue
|
||||||
|
console.log('Initial content set successfully')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to set initial content:', error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 即使没有初始内容,也要确保编辑器是可编辑的
|
||||||
|
editorRef.value.contentEditable = true
|
||||||
|
console.log('Editor initialized without initial content')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 工具配置
|
// 工具配置
|
||||||
const tools = ref([
|
const tools = ref([
|
||||||
{
|
{
|
||||||
@@ -68,6 +88,15 @@ const tools = ref([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 处理输入事件
|
||||||
|
const handleInput = () => {
|
||||||
|
if (editorRef.value) {
|
||||||
|
content.value = editorRef.value.innerHTML
|
||||||
|
console.log('Input event handled, content:', content.value)
|
||||||
|
emit('update:modelValue', content.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检查当前选区是否已经在某种格式中
|
// 检查当前选区是否已经在某种格式中
|
||||||
const isAlreadyInFormat = formatType => {
|
const isAlreadyInFormat = formatType => {
|
||||||
const selection = window.getSelection()
|
const selection = window.getSelection()
|
||||||
@@ -550,14 +579,6 @@ const insertImage = () => {
|
|||||||
fileInput.click()
|
fileInput.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理输入事件
|
|
||||||
const handleInput = () => {
|
|
||||||
if (editorRef.value) {
|
|
||||||
content.value = editorRef.value.innerHTML
|
|
||||||
emit('update:modelValue', content.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理键盘事件
|
// 处理键盘事件
|
||||||
const handleKeydown = e => {
|
const handleKeydown = e => {
|
||||||
// 处理Shift+Enter键换行
|
// 处理Shift+Enter键换行
|
||||||
@@ -710,15 +731,42 @@ const handleToolbarFocusOut = () => {
|
|||||||
}, 200) // 增加延迟时间,确保有足够时间处理点击事件
|
}, 200) // 增加延迟时间,确保有足够时间处理点击事件
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听外部值变化
|
// 暴露方法给父组件
|
||||||
defineExpose({
|
defineExpose({
|
||||||
getContent: () => content.value,
|
getContent: () => content.value,
|
||||||
setContent: newContent => {
|
setContent: newContent => {
|
||||||
content.value = newContent
|
console.log('Setting content in editor:', newContent)
|
||||||
|
content.value = newContent || ''
|
||||||
if (editorRef.value) {
|
if (editorRef.value) {
|
||||||
editorRef.value.innerHTML = newContent
|
try {
|
||||||
|
editorRef.value.innerHTML = content.value
|
||||||
|
console.log('Content set successfully in editorRef')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to set innerHTML:', error)
|
||||||
|
// 备选方案:使用textContent
|
||||||
|
try {
|
||||||
|
editorRef.value.textContent = content.value
|
||||||
|
console.log('Content set using textContent')
|
||||||
|
} catch (textContentError) {
|
||||||
|
console.error('Failed to set textContent:', textContentError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果editorRef还不可用,延迟设置
|
||||||
|
console.log('Editor ref is not available, will retry when mounted')
|
||||||
|
setTimeout(() => {
|
||||||
|
if (editorRef.value) {
|
||||||
|
try {
|
||||||
|
editorRef.value.innerHTML = content.value
|
||||||
|
console.log('Content set successfully after delay')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to set innerHTML after delay:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
insertImage,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -914,31 +962,7 @@ defineExpose({
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 引用格式样式 */
|
|
||||||
:deep(.quote-container) {
|
|
||||||
position: relative;
|
|
||||||
margin: 0 0 12px 0;
|
|
||||||
line-height: var(--editor-line-height, 1.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.quote-icon) {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: var(--editor-font-size, 16px);
|
|
||||||
height: var(--editor-font-size, 16px);
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.quote-content) {
|
|
||||||
border-left: 3px solid var(--primary);
|
|
||||||
padding: 0 var(--editor-font-size, 16px) 0 32px;
|
|
||||||
margin-left: var(--editor-font-size, 16px);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
background-color: var(--background-secondary);
|
|
||||||
font-style: italic;
|
|
||||||
line-height: var(--editor-line-height, 1.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-content img {
|
.editor-content img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -972,71 +996,3 @@ defineExpose({
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
|
||||||
// 插入图片
|
|
||||||
const insertImage = () => {
|
|
||||||
// 创建文件输入元素
|
|
||||||
const fileInput = document.createElement('input')
|
|
||||||
fileInput.type = 'file'
|
|
||||||
fileInput.accept = 'image/*'
|
|
||||||
fileInput.style.display = 'none'
|
|
||||||
|
|
||||||
// 添加到文档中
|
|
||||||
document.body.appendChild(fileInput)
|
|
||||||
|
|
||||||
// 监听文件选择事件
|
|
||||||
fileInput.addEventListener('change', function (event) {
|
|
||||||
const file = event.target.files[0]
|
|
||||||
if (file && file.type.startsWith('image/')) {
|
|
||||||
// 创建FileReader读取文件
|
|
||||||
const reader = new FileReader()
|
|
||||||
reader.onload = function (e) {
|
|
||||||
// 获取图片数据URL
|
|
||||||
const imageDataUrl = e.target.result
|
|
||||||
|
|
||||||
// 获取当前选区
|
|
||||||
const selection = window.getSelection()
|
|
||||||
if (selection.rangeCount > 0) {
|
|
||||||
const range = selection.getRangeAt(0)
|
|
||||||
|
|
||||||
// 创建图片元素
|
|
||||||
const img = document.createElement('img')
|
|
||||||
img.src = imageDataUrl
|
|
||||||
img.className = 'editor-image'
|
|
||||||
img.style.maxWidth = '100%'
|
|
||||||
img.style.height = 'auto'
|
|
||||||
img.style.display = 'block'
|
|
||||||
img.style.margin = '0 auto'
|
|
||||||
|
|
||||||
// 插入图片到当前光标位置
|
|
||||||
range.insertNode(img)
|
|
||||||
|
|
||||||
// 添加换行
|
|
||||||
const br = document.createElement('br')
|
|
||||||
img.parentNode.insertBefore(br, img.nextSibling)
|
|
||||||
|
|
||||||
// 触发输入事件更新内容
|
|
||||||
handleInput()
|
|
||||||
|
|
||||||
// 重新聚焦到编辑器
|
|
||||||
if (editorRef.value) {
|
|
||||||
editorRef.value.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader.readAsDataURL(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理文件输入元素
|
|
||||||
document.body.removeChild(fileInput)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 触发文件选择对话框
|
|
||||||
fileInput.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
insertImage
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -1,29 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<Header
|
<Header title="文件夹" :onBack="() => window.history.back()" />
|
||||||
title="文件夹"
|
<div class="folder-page-container">
|
||||||
:onBack="() => window.history.back()"
|
<div class="search-container">
|
||||||
/>
|
<ion-icon :icon="search" class="search-icon"></ion-icon>
|
||||||
<div style="padding: 10px; background-color: var(--background)">
|
<ion-input placeholder="搜索文件夹..." :value="searchQuery" @ionChange="e => setSearchQuery(e.detail.value)" class="search-input"></ion-input>
|
||||||
<div style="display: flex; align-items: center; background-color: var(--search-bar-background); border-radius: 8px; padding: 0 10px">
|
<ion-button v-if="searchQuery.length > 0" fill="clear" @click="() => setSearchQuery('')">
|
||||||
<ion-icon :icon="search" style="font-size: 20px; color: var(--text-tertiary)"></ion-icon>
|
<ion-icon :icon="closeCircle" class="clear-icon"></ion-icon>
|
||||||
<ion-input
|
|
||||||
placeholder="搜索文件夹..."
|
|
||||||
:value="searchQuery"
|
|
||||||
@ionChange="e => setSearchQuery(e.detail.value)"
|
|
||||||
style="--padding-start: 10px; --padding-end: 10px; flex: 1; font-size: 16px; color: var(--text-primary)"
|
|
||||||
></ion-input>
|
|
||||||
<ion-button
|
|
||||||
v-if="searchQuery.length > 0"
|
|
||||||
fill="clear"
|
|
||||||
@click="() => setSearchQuery('')"
|
|
||||||
>
|
|
||||||
<ion-icon :icon="closeCircle" style="font-size: 20px; color: var(--text-tertiary)"></ion-icon>
|
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list style="background-color: var(--background); padding: 0 16px; --ion-item-background: var(--background)">
|
<ion-list class="folder-list">
|
||||||
<FolderManage
|
<FolderManage
|
||||||
:allCount="allNotesCount"
|
:allCount="allNotesCount"
|
||||||
:starredCount="starredNotesCount"
|
:starredCount="starredNotesCount"
|
||||||
@@ -33,47 +21,46 @@
|
|||||||
:onAllClick="() => handleFolderPress('all')"
|
:onAllClick="() => handleFolderPress('all')"
|
||||||
:onStarredClick="() => handleFolderPress('starred')"
|
:onStarredClick="() => handleFolderPress('starred')"
|
||||||
:onTrashClick="() => handleFolderPress('trash')"
|
:onTrashClick="() => handleFolderPress('trash')"
|
||||||
:onArchiveClick="() => handleFolderPress('archive')"
|
:onArchiveClick="() => handleFolderPress('archive')" />
|
||||||
/>
|
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useAppStore } from '../stores/useAppStore';
|
import { useAppStore } from '../stores/useAppStore'
|
||||||
import { search, closeCircle } from 'ionicons/icons';
|
import { search, closeCircle } from 'ionicons/icons'
|
||||||
import FolderManage from '../components/FolderManage.vue';
|
import FolderManage from '../components/FolderManage.vue'
|
||||||
import Header from '../components/Header.vue';
|
import Header from '../components/Header.vue'
|
||||||
|
|
||||||
const store = useAppStore();
|
const store = useAppStore()
|
||||||
|
|
||||||
// 加载初始数据
|
// 加载初始数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
store.loadData();
|
store.loadData()
|
||||||
});
|
})
|
||||||
|
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('')
|
||||||
const selectedFolder = ref('all');
|
const selectedFolder = ref('all')
|
||||||
|
|
||||||
// Calculate note count for each folder
|
// Calculate note count for each folder
|
||||||
const foldersWithCount = computed(() => {
|
const foldersWithCount = computed(() => {
|
||||||
return store.folders.map(folder => {
|
return store.folders.map(folder => {
|
||||||
const noteCount = store.notes.filter(note => note.folderId === folder.id).length;
|
const noteCount = store.notes.filter(note => note.folderId === folder.id).length
|
||||||
return {
|
return {
|
||||||
...folder,
|
...folder,
|
||||||
noteCount,
|
noteCount,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// Add default folders at the beginning
|
// Add default folders at the beginning
|
||||||
const allNotesCount = computed(() => store.notes.length);
|
const allNotesCount = computed(() => store.notes.length)
|
||||||
const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length);
|
const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length)
|
||||||
// Assuming we have a way to track deleted notes in the future
|
// Assuming we have a way to track deleted notes in the future
|
||||||
const trashNotesCount = 0;
|
const trashNotesCount = 0
|
||||||
const archiveCount = 0;
|
const archiveCount = 0
|
||||||
|
|
||||||
const foldersWithAllNotes = computed(() => {
|
const foldersWithAllNotes = computed(() => {
|
||||||
return [
|
return [
|
||||||
@@ -81,39 +68,76 @@ const foldersWithAllNotes = computed(() => {
|
|||||||
{ id: 'starred', name: '加星便签', noteCount: starredNotesCount.value, createdAt: new Date() },
|
{ id: 'starred', name: '加星便签', noteCount: starredNotesCount.value, createdAt: new Date() },
|
||||||
{ id: 'trash', name: '回收站', noteCount: trashNotesCount, createdAt: new Date() },
|
{ id: 'trash', name: '回收站', noteCount: trashNotesCount, createdAt: new Date() },
|
||||||
...foldersWithCount.value,
|
...foldersWithCount.value,
|
||||||
];
|
]
|
||||||
});
|
})
|
||||||
|
|
||||||
const handleFolderPress = (folderId) => {
|
const handleFolderPress = folderId => {
|
||||||
// 更新选中的文件夹状态
|
// 更新选中的文件夹状态
|
||||||
selectedFolder.value = folderId;
|
selectedFolder.value = folderId
|
||||||
// 在实际应用中,这里会将选中的文件夹传递回NoteListScreen
|
// 在实际应用中,这里会将选中的文件夹传递回NoteListScreen
|
||||||
// 通过导航参数传递选中的文件夹ID
|
// 通过导航参数传递选中的文件夹ID
|
||||||
window.location.hash = `#/notes?folder=${folderId}`;
|
window.location.hash = `#/notes?folder=${folderId}`
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleAddFolder = () => {
|
const handleAddFolder = () => {
|
||||||
// In a full implementation, this would open a folder creation dialog
|
// In a full implementation, this would open a folder creation dialog
|
||||||
console.log('Add folder pressed');
|
console.log('Add folder pressed')
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
// In a full implementation, this would filter folders based on searchQuery
|
// In a full implementation, this would filter folders based on searchQuery
|
||||||
console.log('Search for:', searchQuery.value);
|
console.log('Search for:', searchQuery.value)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleBackPress = () => {
|
const handleBackPress = () => {
|
||||||
window.history.back();
|
window.history.back()
|
||||||
};
|
}
|
||||||
|
|
||||||
// Filter folders based on search query
|
// Filter folders based on search query
|
||||||
const filteredFolders = computed(() => {
|
const filteredFolders = computed(() => {
|
||||||
return foldersWithAllNotes.value.filter(folder =>
|
return foldersWithAllNotes.value.filter(folder => folder.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||||
folder.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
})
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const setSearchQuery = (value) => {
|
const setSearchQuery = value => {
|
||||||
searchQuery.value = value;
|
searchQuery.value = value
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.folder-page-container {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--search-bar-background);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
--padding-start: 10px;
|
||||||
|
--padding-end: 10px;
|
||||||
|
flex: 1;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-list {
|
||||||
|
background-color: var(--background);
|
||||||
|
padding: 0 16px;
|
||||||
|
--ion-item-background: var(--background);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<Header :onBack="handleCancel" :onAction="handleAction" actionIcon="save" />
|
<Header :onBack="handleCancel" :onAction="handleAction" actionIcon="save" />
|
||||||
|
|
||||||
<!-- 顶部信息栏 -->
|
<!-- 顶部信息栏 -->
|
||||||
<div class="header-info" v-if="isEditing">
|
<div class="header-info">
|
||||||
<span class="edit-time">{{ formattedTime }}</span>
|
<span class="edit-time">{{ formattedTime }}</span>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<span class="word-count">{{ wordCount }}</span>
|
<span class="word-count">{{ wordCount }}</span>
|
||||||
@@ -12,7 +12,11 @@
|
|||||||
|
|
||||||
<!-- 富文本编辑器 -->
|
<!-- 富文本编辑器 -->
|
||||||
<div class="editor-container">
|
<div class="editor-container">
|
||||||
<RichTextEditor ref="editorRef" v-model="content" class="rich-text-editor" />
|
<RichTextEditor
|
||||||
|
ref="editorRef"
|
||||||
|
:modelValue="content"
|
||||||
|
@update:modelValue="handleContentChange"
|
||||||
|
class="rich-text-editor" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ion-alert
|
<ion-alert
|
||||||
@@ -35,34 +39,123 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||||
import { useAppStore } from '../stores/useAppStore'
|
import { useAppStore } from '../stores/useAppStore'
|
||||||
import Header from '../components/Header.vue'
|
import Header from '../components/Header.vue'
|
||||||
import RichTextEditor from '../components/RichTextEditor.vue'
|
import RichTextEditor from '../components/RichTextEditor.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
noteId: {
|
id: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 为了保持向后兼容性,我们也支持noteId属性
|
||||||
|
const noteId = computed(() => props.id || props.noteId)
|
||||||
|
|
||||||
const store = useAppStore()
|
const store = useAppStore()
|
||||||
const editorRef = ref(null)
|
const editorRef = ref(null)
|
||||||
|
|
||||||
|
// 设置便签内容的函数
|
||||||
|
const setNoteContent = async (noteId) => {
|
||||||
|
// 确保store数据已加载
|
||||||
|
if (store.notes.length === 0) {
|
||||||
|
await store.loadData()
|
||||||
|
console.log('Store loaded, notes count:', store.notes.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = store.notes.find(n => n.id === noteId)
|
||||||
|
console.log('Found note:', note)
|
||||||
|
|
||||||
|
// 确保编辑器已经初始化
|
||||||
|
await nextTick()
|
||||||
|
console.log('Editor ref:', editorRef.value)
|
||||||
|
|
||||||
|
if (note) {
|
||||||
|
console.log('Setting content:', note.content)
|
||||||
|
// 无论editorRef是否可用,都先设置content的值
|
||||||
|
content.value = note.content || ''
|
||||||
|
// 如果editorRef可用,直接设置内容
|
||||||
|
if (editorRef.value) {
|
||||||
|
editorRef.value.setContent(note.content || '')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Note not available')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载初始数据
|
// 加载初始数据
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
console.log('NoteEditorPage mounted')
|
||||||
await store.loadData()
|
await store.loadData()
|
||||||
|
console.log('Store loaded, notes count:', store.notes.length)
|
||||||
|
|
||||||
|
// 如果是编辑现有便签,在组件挂载后设置内容
|
||||||
|
if (noteId.value) {
|
||||||
|
await setNoteContent(noteId.value)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听noteId变化,确保在编辑器准备好后设置内容
|
||||||
|
watch(noteId, async (newNoteId) => {
|
||||||
|
console.log('Note ID changed:', newNoteId)
|
||||||
|
if (newNoteId) {
|
||||||
|
await setNoteContent(newNoteId)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 监听store变化,确保在store加载后设置内容
|
||||||
|
watch(() => store.notes, async (newNotes) => {
|
||||||
|
if (noteId.value && newNotes.length > 0) {
|
||||||
|
await setNoteContent(noteId.value)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
// Check if we're editing an existing note
|
// Check if we're editing an existing note
|
||||||
const isEditing = !!props.noteId
|
const isEditing = !!noteId.value
|
||||||
const existingNote = isEditing ? store.notes.find(n => n.id === props.noteId) : null
|
const existingNote = isEditing ? store.notes.find(n => n.id === noteId.value) : null
|
||||||
|
|
||||||
// Initialize state with existing note data or empty strings
|
// Initialize state with existing note data or empty strings
|
||||||
const content = ref(existingNote?.content || '')
|
const content = ref(existingNote?.content || '')
|
||||||
|
|
||||||
|
// 当组件挂载时,确保编辑器初始化为空内容(针对新建便签)
|
||||||
|
onMounted(() => {
|
||||||
|
if (!isEditing && editorRef.value) {
|
||||||
|
console.log('Initializing editor for new note')
|
||||||
|
editorRef.value.setContent('')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听store变化,确保在store加载后设置内容
|
||||||
|
watch(() => store.notes, async (newNotes) => {
|
||||||
|
if (noteId.value && newNotes.length > 0) {
|
||||||
|
await setNoteContent(noteId.value)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
const showAlert = ref(false)
|
const showAlert = ref(false)
|
||||||
|
|
||||||
|
// 防抖函数
|
||||||
|
const debounce = (func, delay) => {
|
||||||
|
let timeoutId
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
timeoutId = setTimeout(() => func.apply(this, args), delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖处理内容变化
|
||||||
|
const debouncedHandleContentChange = debounce((newContent) => {
|
||||||
|
content.value = newContent
|
||||||
|
console.log('Content updated:', newContent)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
// 监听编辑器内容变化
|
||||||
|
const handleContentChange = (newContent) => {
|
||||||
|
console.log('Editor content changed:', newContent)
|
||||||
|
debouncedHandleContentChange(newContent)
|
||||||
|
}
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const formattedTime = computed(() => {
|
const formattedTime = computed(() => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
@@ -93,15 +186,18 @@ const wordCount = computed(() => {
|
|||||||
// 处理保存
|
// 处理保存
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 获取编辑器中的实际内容
|
||||||
|
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
|
||||||
|
|
||||||
if (isEditing && existingNote) {
|
if (isEditing && existingNote) {
|
||||||
// Update existing note
|
// Update existing note
|
||||||
await store.updateNote(props.noteId, {
|
await store.updateNote(noteId.value, {
|
||||||
content: content.value,
|
content: editorContent,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Create new note
|
// Create new note
|
||||||
await store.addNote({
|
await store.addNote({
|
||||||
content: content.value,
|
content: editorContent,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -129,9 +225,12 @@ const handleCancel = () => {
|
|||||||
// 处理创建(用于新建便签)
|
// 处理创建(用于新建便签)
|
||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 获取编辑器中的实际内容
|
||||||
|
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
|
||||||
|
|
||||||
// Create new note
|
// Create new note
|
||||||
await store.addNote({
|
await store.addNote({
|
||||||
content: content.value,
|
content: editorContent,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -185,7 +284,7 @@ const setShowAlert = value => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 8px 16px;
|
padding: 1.5rem 16px 0.7rem 16px;
|
||||||
background-color: var(--background-card);
|
background-color: var(--background-card);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
|
|||||||
@@ -6,41 +6,26 @@
|
|||||||
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
|
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
|
||||||
<div
|
<div
|
||||||
v-if="isFolderExpanded"
|
v-if="isFolderExpanded"
|
||||||
style="position: absolute; top: 50px; left: 10%; right: 10%; z-index: 1000; background-color: var(--background-card); border-radius: 8px; box-shadow: 0 2px 4px var(--shadow); border: 1px solid #f0ece7; overflow: hidden">
|
class="folder-list">
|
||||||
<FolderManage
|
<FolderManage
|
||||||
:allCount="notes.length"
|
:allCount="allNotesCount"
|
||||||
:starredCount="starredNotesCount"
|
:starredCount="starredNotesCount"
|
||||||
:trashCount="0"
|
:trashCount="trashNotesCount"
|
||||||
:archiveCount="0"
|
:archiveCount="0"
|
||||||
:selectedFolder="currentFolder"
|
:selectedFolder="currentFolder"
|
||||||
:onAllClick="
|
:onAllClick="handleAllNotesClick"
|
||||||
() => {
|
:onStarredClick="handleStarredNotesClick"
|
||||||
setCurrentFolder('all')
|
:onTrashClick="handleTrashNotesClick" />
|
||||||
setIsFolderExpanded(false)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
:onStarredClick="
|
|
||||||
() => {
|
|
||||||
setCurrentFolder('starred')
|
|
||||||
setIsFolderExpanded(false)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
:onTrashClick="
|
|
||||||
() => {
|
|
||||||
setCurrentFolder('trash')
|
|
||||||
setIsFolderExpanded(false)
|
|
||||||
}
|
|
||||||
" />
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
|
<!-- 点击外部区域收起文件夹列表的覆盖层 -->
|
||||||
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: transparent; z-index: 99"></div>
|
<div v-if="isFolderExpanded" @click="() => setIsFolderExpanded(false)" class="folder-overlay"></div>
|
||||||
|
|
||||||
<div style="padding: 0.8rem 0.5rem">
|
<div class="search-container">
|
||||||
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
|
<SearchBar v-model="searchQuery" @search="handleSearch" @clear="handleClearSearch" @focus="handleSearchFocus" @blur="handleSearchBlur" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="flex: 1">
|
<div class="notes-container">
|
||||||
<div v-for="note in filteredAndSortedNotes" :key="note.id" style="margin: 0.4rem 0">
|
<div v-for="note in filteredAndSortedNotes" :key="note.id" class="note-item">
|
||||||
<NoteItem
|
<NoteItem
|
||||||
:title="note.title"
|
:title="note.title"
|
||||||
:content="note.content"
|
:content="note.content"
|
||||||
@@ -76,7 +61,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useAppStore } from '../stores/useAppStore'
|
import { useAppStore } from '../stores/useAppStore'
|
||||||
import { create, settings } from 'ionicons/icons'
|
|
||||||
import NoteItem from '../components/NoteItem.vue'
|
import NoteItem from '../components/NoteItem.vue'
|
||||||
import Header from '../components/Header.vue'
|
import Header from '../components/Header.vue'
|
||||||
import FolderManage from '../components/FolderManage.vue'
|
import FolderManage from '../components/FolderManage.vue'
|
||||||
@@ -102,50 +86,67 @@ const currentFolder = ref('all') // 默认文件夹是"全部便签"
|
|||||||
const showAlert = ref(false)
|
const showAlert = ref(false)
|
||||||
const noteToDelete = ref(null)
|
const noteToDelete = ref(null)
|
||||||
|
|
||||||
// 计算加星便签数量
|
// 计算加星便签数量(未删除的)
|
||||||
const starredNotesCount = computed(() => {
|
const starredNotesCount = computed(() => {
|
||||||
return store.notes.filter(note => note.isStarred).length
|
return store.notes.filter(note => note.isStarred && !note.isDeleted).length
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算置顶便签数量
|
// 计算回收站便签数量
|
||||||
const topNotesCount = computed(() => {
|
const trashNotesCount = computed(() => {
|
||||||
return filteredAndSortedNotes.value.filter(note => note.isTop).length
|
return store.notes.filter(note => note.isDeleted).length
|
||||||
})
|
})
|
||||||
|
|
||||||
// 根据当前文件夹过滤便签
|
// 根据当前文件夹过滤便签
|
||||||
const filteredNotes = computed(() => {
|
const filteredNotes = computed(() => {
|
||||||
|
// 预处理搜索查询,提高性能
|
||||||
|
const lowerCaseQuery = searchQuery.value.toLowerCase().trim()
|
||||||
|
|
||||||
return store.notes.filter(note => {
|
return store.notes.filter(note => {
|
||||||
|
// 先检查搜索条件
|
||||||
|
const matchesSearch = !lowerCaseQuery ||
|
||||||
|
note.title.toLowerCase().includes(lowerCaseQuery) ||
|
||||||
|
note.content.toLowerCase().includes(lowerCaseQuery)
|
||||||
|
|
||||||
|
if (!matchesSearch) return false
|
||||||
|
|
||||||
|
// 再检查文件夹条件
|
||||||
switch (currentFolder.value) {
|
switch (currentFolder.value) {
|
||||||
case 'all':
|
case 'all':
|
||||||
return true
|
// 全部便签中不显示已删除的便签
|
||||||
|
return !note.isDeleted
|
||||||
case 'starred':
|
case 'starred':
|
||||||
return note.isStarred
|
// 加星便签中只显示未删除的加星便签
|
||||||
|
return note.isStarred && !note.isDeleted
|
||||||
case 'trash':
|
case 'trash':
|
||||||
// 假设我们有一个isDeleted属性来标识已删除的便签
|
// 回收站中只显示已删除的便签
|
||||||
return note.isDeleted || false
|
return note.isDeleted
|
||||||
default:
|
default:
|
||||||
return note.folderId === currentFolder.value
|
// 自定义文件夹中不显示已删除的便签
|
||||||
|
return note.folderId === currentFolder.value && !note.isDeleted
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Filter and sort notes
|
// Filter and sort notes
|
||||||
const filteredAndSortedNotes = computed(() => {
|
const filteredAndSortedNotes = computed(() => {
|
||||||
return filteredNotes.value
|
return [...filteredNotes.value].sort((a, b) => {
|
||||||
.filter(note => note.title.toLowerCase().includes(searchQuery.value.toLowerCase()) || note.content.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
// 置顶的便签排在前面
|
||||||
.sort((a, b) => {
|
if (a.isTop && !b.isTop) return -1
|
||||||
// 置顶的便签排在前面
|
if (!a.isTop && b.isTop) return 1
|
||||||
if (a.isTop && !b.isTop) return -1
|
|
||||||
if (!a.isTop && b.isTop) return 1
|
|
||||||
|
|
||||||
if (sortBy.value === 'title') {
|
// 根据排序方式排序
|
||||||
|
switch (sortBy.value) {
|
||||||
|
case 'title':
|
||||||
return a.title.localeCompare(b.title)
|
return a.title.localeCompare(b.title)
|
||||||
} else if (sortBy.value === 'starred') {
|
case 'starred':
|
||||||
|
// 加星的便签排在前面
|
||||||
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
|
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
|
||||||
} else {
|
case 'date':
|
||||||
|
default:
|
||||||
|
// 按更新时间倒序排列(最新的在前)
|
||||||
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算头部标题
|
// 计算头部标题
|
||||||
@@ -158,10 +159,15 @@ const headerTitle = computed(() => {
|
|||||||
case 'trash':
|
case 'trash':
|
||||||
return '回收站'
|
return '回收站'
|
||||||
default:
|
default:
|
||||||
return '文件夹'
|
return '全部便签'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 计算全部便签数量(未删除的)
|
||||||
|
const allNotesCount = computed(() => {
|
||||||
|
return store.notes.filter(note => !note.isDeleted).length
|
||||||
|
})
|
||||||
|
|
||||||
const handleNotePress = noteId => {
|
const handleNotePress = noteId => {
|
||||||
// 导航到编辑页面的逻辑将在路由中处理
|
// 导航到编辑页面的逻辑将在路由中处理
|
||||||
window.location.hash = `#/editor/${noteId}`
|
window.location.hash = `#/editor/${noteId}`
|
||||||
@@ -187,21 +193,44 @@ const handleDeleteNote = noteId => {
|
|||||||
const handleStarToggle = async noteId => {
|
const handleStarToggle = async noteId => {
|
||||||
const note = store.notes.find(n => n.id === noteId)
|
const note = store.notes.find(n => n.id === noteId)
|
||||||
if (note) {
|
if (note) {
|
||||||
await store.updateNote(noteId, { isStarred: !note.isStarred })
|
try {
|
||||||
|
await store.updateNote(noteId, { isStarred: !note.isStarred })
|
||||||
|
console.log(`Note ${noteId} starred status updated`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update note star status:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTopToggle = async noteId => {
|
const handleTopToggle = async noteId => {
|
||||||
const note = store.notes.find(n => n.id === noteId)
|
const note = store.notes.find(n => n.id === noteId)
|
||||||
if (note) {
|
if (note) {
|
||||||
await store.updateNote(noteId, { isTop: !note.isTop })
|
try {
|
||||||
|
await store.updateNote(noteId, { isTop: !note.isTop })
|
||||||
|
console.log(`Note ${noteId} top status updated`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to update note top status:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmDeleteNote = () => {
|
const confirmDeleteNote = async () => {
|
||||||
if (noteToDelete.value) {
|
if (noteToDelete.value) {
|
||||||
store.deleteNote(noteToDelete.value)
|
try {
|
||||||
noteToDelete.value = null
|
// 检查当前是否在回收站中
|
||||||
|
if (currentFolder.value === 'trash') {
|
||||||
|
// 在回收站中删除便签,彻底删除
|
||||||
|
await store.permanentlyDeleteNote(noteToDelete.value)
|
||||||
|
console.log(`Note ${noteToDelete.value} permanently deleted`)
|
||||||
|
} else {
|
||||||
|
// 不在回收站中,将便签移至回收站
|
||||||
|
await store.moveToTrash(noteToDelete.value)
|
||||||
|
console.log(`Note ${noteToDelete.value} moved to trash`)
|
||||||
|
}
|
||||||
|
noteToDelete.value = null
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to delete note:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showAlert.value = false
|
showAlert.value = false
|
||||||
}
|
}
|
||||||
@@ -215,6 +244,21 @@ const handleSort = () => {
|
|||||||
console.log('Sort by:', sortOptions[nextIndex])
|
console.log('Sort by:', sortOptions[nextIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleAllNotesClick = () => {
|
||||||
|
setCurrentFolder('all')
|
||||||
|
setIsFolderExpanded(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStarredNotesClick = () => {
|
||||||
|
setCurrentFolder('starred')
|
||||||
|
setIsFolderExpanded(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTrashNotesClick = () => {
|
||||||
|
setCurrentFolder('trash')
|
||||||
|
setIsFolderExpanded(false)
|
||||||
|
}
|
||||||
|
|
||||||
const handleFolderPress = () => {
|
const handleFolderPress = () => {
|
||||||
// 导航到文件夹页面的逻辑将在路由中处理
|
// 导航到文件夹页面的逻辑将在路由中处理
|
||||||
window.location.hash = '#/folders'
|
window.location.hash = '#/folders'
|
||||||
@@ -228,29 +272,77 @@ const handleSettingsPress = () => {
|
|||||||
const handleFolderToggle = () => {
|
const handleFolderToggle = () => {
|
||||||
// 在实际应用中,这里会触发文件夹列表的展开/收起
|
// 在实际应用中,这里会触发文件夹列表的展开/收起
|
||||||
isFolderExpanded.value = !isFolderExpanded.value
|
isFolderExpanded.value = !isFolderExpanded.value
|
||||||
console.log('Folder expanded:', !isFolderExpanded.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = query => {
|
const handleSearch = query => {
|
||||||
// 搜索功能已在computed属性filteredAndSortedNotes中实现
|
// 搜索功能已在computed属性filteredAndSortedNotes中实现
|
||||||
console.log('Search for:', query)
|
console.log('Search for:', query)
|
||||||
|
|
||||||
|
// 可以在这里添加搜索统计或其它功能
|
||||||
|
if (query && query.length > 0) {
|
||||||
|
console.log(`Found ${filteredAndSortedNotes.value.length} matching notes`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClearSearch = () => {
|
const handleClearSearch = () => {
|
||||||
// 清除搜索已在v-model中处理
|
// 清除搜索已在v-model中处理
|
||||||
console.log('Search cleared')
|
console.log('Search cleared')
|
||||||
|
|
||||||
|
// 清除搜索后可以重置一些状态
|
||||||
|
setSearchQuery('')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearchFocus = () => {
|
const handleSearchFocus = () => {
|
||||||
console.log('Search bar focused')
|
console.log('Search bar focused')
|
||||||
|
// 可以在这里添加获得焦点时的特殊处理
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSearchBlur = () => {
|
const handleSearchBlur = () => {
|
||||||
console.log('Search bar blurred')
|
console.log('Search bar blurred')
|
||||||
|
// 可以在这里添加失去焦点时的特殊处理
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 防抖搜索函数,避免频繁触发搜索
|
||||||
|
const debounceSearch = (func, delay) => {
|
||||||
|
let timeoutId
|
||||||
|
return function (...args) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
timeoutId = setTimeout(() => func.apply(this, args), delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖搜索处理
|
||||||
|
const debouncedHandleSearch = debounceSearch((query) => {
|
||||||
|
handleSearch(query)
|
||||||
|
}, 300)
|
||||||
|
|
||||||
|
// 改进的日期格式化函数
|
||||||
const formatDate = dateString => {
|
const formatDate = dateString => {
|
||||||
return new Date(dateString).toLocaleDateString()
|
const date = new Date(dateString)
|
||||||
|
const now = new Date()
|
||||||
|
|
||||||
|
// 计算日期差
|
||||||
|
const diffTime = now - date
|
||||||
|
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
|
||||||
|
|
||||||
|
// 今天的便签显示时间
|
||||||
|
if (diffDays === 0) {
|
||||||
|
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 昨天的便签显示"昨天"
|
||||||
|
if (diffDays === 1) {
|
||||||
|
return '昨天'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一周内的便签显示星期几
|
||||||
|
if (diffDays < 7) {
|
||||||
|
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||||
|
return weekdays[date.getDay()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 超过一周的便签显示月日
|
||||||
|
return `${date.getMonth() + 1}/${date.getDate()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const setCurrentFolder = folder => {
|
const setCurrentFolder = folder => {
|
||||||
@@ -278,4 +370,39 @@ const notes = computed(() => store.notes)
|
|||||||
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
|
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
.folder-list {
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
left: 10%;
|
||||||
|
right: 10%;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: var(--background-card);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px var(--shadow);
|
||||||
|
border: 1px solid #f0ece7;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
padding: 0.8rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-container {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-item {
|
||||||
|
margin: 0.4rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,31 +5,31 @@
|
|||||||
:onBack="handleBackPress"
|
:onBack="handleBackPress"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ion-content style="background-color: var(--background)">
|
<ion-content class="settings-content">
|
||||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
<div class="settings-section">
|
||||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
<div class="section-header">
|
||||||
账户
|
账户
|
||||||
</div>
|
</div>
|
||||||
<div button @click="handleLogin" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
<div button @click="handleLogin" class="settings-item settings-item-clickable">
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">登录云同步</div>
|
<div class="item-text-primary">登录云同步</div>
|
||||||
<div style="font-size: 15px; color: var(--text-tertiary)">未登录</div>
|
<div class="item-text-tertiary">未登录</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
<div class="settings-section">
|
||||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
<div class="section-header">
|
||||||
偏好设置
|
偏好设置
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border)">
|
<div class="settings-item settings-item-border">
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">云同步</div>
|
<div class="item-text-primary">云同步</div>
|
||||||
<ion-toggle
|
<ion-toggle
|
||||||
slot="end"
|
slot="end"
|
||||||
:checked="settings.cloudSync"
|
:checked="settings.cloudSync"
|
||||||
@ion-change="toggleCloudSync"
|
@ion-change="toggleCloudSync"
|
||||||
></ion-toggle>
|
></ion-toggle>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px">
|
<div class="settings-item">
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">深色模式</div>
|
<div class="item-text-primary">深色模式</div>
|
||||||
<ion-toggle
|
<ion-toggle
|
||||||
slot="end"
|
slot="end"
|
||||||
:checked="settings.darkMode"
|
:checked="settings.darkMode"
|
||||||
@@ -38,41 +38,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
<div class="settings-section">
|
||||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
<div class="section-header">
|
||||||
数据管理
|
数据管理
|
||||||
</div>
|
</div>
|
||||||
<div button @click="handleBackup" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
<div button @click="handleBackup" class="settings-item settings-item-clickable settings-item-border">
|
||||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_save_pic.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
<img :src="'/assets/icons/drawable-xxhdpi/btn_save_pic.png'" class="item-icon" />
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">备份便签</div>
|
<div class="item-text-primary">备份便签</div>
|
||||||
</div>
|
</div>
|
||||||
<div button @click="handleRestore" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
<div button @click="handleRestore" class="settings-item settings-item-clickable settings-item-border">
|
||||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_restore.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
<img :src="'/assets/icons/drawable-xxhdpi/btn_restore.png'" class="item-icon" />
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">恢复便签</div>
|
<div class="item-text-primary">恢复便签</div>
|
||||||
</div>
|
</div>
|
||||||
<div button @click="handleExport" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
<div button @click="handleExport" class="settings-item settings-item-clickable settings-item-border">
|
||||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_share.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
<img :src="'/assets/icons/drawable-xxhdpi/btn_share.png'" class="item-icon" />
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">导出便签</div>
|
<div class="item-text-primary">导出便签</div>
|
||||||
</div>
|
</div>
|
||||||
<div button @click="handleImport" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; cursor: pointer">
|
<div button @click="handleImport" class="settings-item settings-item-clickable">
|
||||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_load_error.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
<img :src="'/assets/icons/drawable-xxhdpi/btn_load_error.png'" class="item-icon" />
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">导入便签</div>
|
<div class="item-text-primary">导入便签</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
<div class="settings-section">
|
||||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
<div class="section-header">
|
||||||
关于
|
关于
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border)">
|
<div class="settings-item settings-item-border">
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">版本</div>
|
<div class="item-text-primary">版本</div>
|
||||||
<div style="font-size: 15px; color: var(--text-tertiary)">1.0.0</div>
|
<div class="item-text-tertiary">1.0.0</div>
|
||||||
</div>
|
</div>
|
||||||
<div button @click="handlePrivacyPolicy" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; border-bottom: 1px solid var(--border); cursor: pointer">
|
<div button @click="handlePrivacyPolicy" class="settings-item settings-item-clickable settings-item-border">
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">隐私政策</div>
|
<div class="item-text-primary">隐私政策</div>
|
||||||
</div>
|
</div>
|
||||||
<div button @click="handleTermsOfService" style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px; cursor: pointer">
|
<div button @click="handleTermsOfService" class="settings-item settings-item-clickable">
|
||||||
<div style="font-size: 16px; color: var(--text-primary)">服务条款</div>
|
<div class="item-text-primary">服务条款</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@@ -139,4 +139,65 @@ const handleBackPress = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const settings = computed(() => store.settings);
|
const settings = computed(() => store.settings);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.settings-content {
|
||||||
|
background-color: var(--background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
background-color: var(--background-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
padding: 10px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--background-card);
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item-border {
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item-clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-text-primary {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-text-tertiary {
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item-clickable .item-text-primary {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item-clickable {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,44 +1,44 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia'
|
||||||
import * as storage from '../utils/storage';
|
import * as storage from '../utils/storage'
|
||||||
|
|
||||||
export const useAppStore = defineStore('app', {
|
export const useAppStore = defineStore('app', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
notes: [],
|
notes: [],
|
||||||
folders: [],
|
folders: [],
|
||||||
settings: { cloudSync: false, darkMode: false }
|
settings: { cloudSync: false, darkMode: false },
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
starredNotesCount: (state) => {
|
starredNotesCount: state => {
|
||||||
return state.notes.filter(note => note.isStarred).length;
|
return state.notes.filter(note => note.isStarred).length
|
||||||
|
},
|
||||||
|
|
||||||
|
allNotesCount: state => {
|
||||||
|
return state.notes.length
|
||||||
},
|
},
|
||||||
|
|
||||||
allNotesCount: (state) => {
|
|
||||||
return state.notes.length;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
async loadData() {
|
async loadData() {
|
||||||
try {
|
try {
|
||||||
const loadedNotes = await storage.getNotes();
|
const loadedNotes = await storage.getNotes()
|
||||||
const loadedFolders = await storage.getFolders();
|
const loadedFolders = await storage.getFolders()
|
||||||
const loadedSettings = await storage.getSettings();
|
const loadedSettings = await storage.getSettings()
|
||||||
|
|
||||||
// 如果没有数据,则加载mock数据
|
// 如果没有数据,则加载mock数据
|
||||||
if (loadedNotes.length === 0 && loadedFolders.length === 0) {
|
if (loadedNotes.length === 0 && loadedFolders.length === 0) {
|
||||||
this.loadMockData();
|
this.loadMockData()
|
||||||
} else {
|
} else {
|
||||||
this.notes = loadedNotes;
|
this.notes = loadedNotes
|
||||||
this.folders = loadedFolders;
|
this.folders = loadedFolders
|
||||||
this.settings = loadedSettings;
|
this.settings = loadedSettings
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading data:', error);
|
console.error('Error loading data:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 加载mock数据
|
// 加载mock数据
|
||||||
async loadMockData() {
|
async loadMockData() {
|
||||||
// Mock notes
|
// Mock notes
|
||||||
@@ -52,7 +52,9 @@ export const useAppStore = defineStore('app', {
|
|||||||
folderId: null,
|
folderId: null,
|
||||||
isStarred: true,
|
isStarred: true,
|
||||||
isTop: true,
|
isTop: true,
|
||||||
hasImage: false
|
hasImage: false,
|
||||||
|
isDeleted: false,
|
||||||
|
deletedAt: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
@@ -63,7 +65,9 @@ export const useAppStore = defineStore('app', {
|
|||||||
folderId: null,
|
folderId: null,
|
||||||
isStarred: true,
|
isStarred: true,
|
||||||
isTop: false,
|
isTop: false,
|
||||||
hasImage: true
|
hasImage: true,
|
||||||
|
isDeleted: false,
|
||||||
|
deletedAt: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
@@ -74,7 +78,9 @@ export const useAppStore = defineStore('app', {
|
|||||||
folderId: null,
|
folderId: null,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
isTop: false,
|
isTop: false,
|
||||||
hasImage: false
|
hasImage: false,
|
||||||
|
isDeleted: false,
|
||||||
|
deletedAt: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
@@ -85,7 +91,9 @@ export const useAppStore = defineStore('app', {
|
|||||||
folderId: null,
|
folderId: null,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
isTop: false,
|
isTop: false,
|
||||||
hasImage: false
|
hasImage: false,
|
||||||
|
isDeleted: false,
|
||||||
|
deletedAt: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5',
|
id: '5',
|
||||||
@@ -96,145 +104,191 @@ export const useAppStore = defineStore('app', {
|
|||||||
folderId: null,
|
folderId: null,
|
||||||
isStarred: false,
|
isStarred: false,
|
||||||
isTop: false,
|
isTop: false,
|
||||||
hasImage: false
|
hasImage: false,
|
||||||
}
|
isDeleted: false,
|
||||||
];
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
title: '已删除的便签',
|
||||||
|
content: '这是一条已删除的便签示例,应该只在回收站中显示。',
|
||||||
|
createdAt: new Date(Date.now() - 432000000).toISOString(), // 5天前
|
||||||
|
updatedAt: new Date(Date.now() - 432000000).toISOString(),
|
||||||
|
folderId: null,
|
||||||
|
isStarred: false,
|
||||||
|
isTop: false,
|
||||||
|
hasImage: false,
|
||||||
|
isDeleted: true,
|
||||||
|
deletedAt: new Date(Date.now() - 86400000).toISOString(), // 1天前删除
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
// Mock folders
|
// Mock folders
|
||||||
const mockFolders = [
|
const mockFolders = [
|
||||||
{
|
{
|
||||||
id: 'folder1',
|
id: 'folder1',
|
||||||
name: '工作',
|
name: '工作',
|
||||||
createdAt: new Date().toISOString()
|
createdAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'folder2',
|
id: 'folder2',
|
||||||
name: '个人',
|
name: '个人',
|
||||||
createdAt: new Date().toISOString()
|
createdAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'folder3',
|
id: 'folder3',
|
||||||
name: '学习',
|
name: '学习',
|
||||||
createdAt: new Date().toISOString()
|
createdAt: new Date().toISOString(),
|
||||||
}
|
},
|
||||||
];
|
]
|
||||||
|
|
||||||
// Mock settings
|
// Mock settings
|
||||||
const mockSettings = {
|
const mockSettings = {
|
||||||
cloudSync: false,
|
cloudSync: false,
|
||||||
darkMode: false
|
darkMode: false,
|
||||||
};
|
}
|
||||||
|
|
||||||
this.notes = mockNotes;
|
this.notes = mockNotes
|
||||||
this.folders = mockFolders;
|
this.folders = mockFolders
|
||||||
this.settings = mockSettings;
|
this.settings = mockSettings
|
||||||
|
|
||||||
// 保存到localStorage
|
// 保存到localStorage
|
||||||
await storage.saveNotes(mockNotes);
|
await storage.saveNotes(mockNotes)
|
||||||
await storage.saveFolders(mockFolders);
|
await storage.saveFolders(mockFolders)
|
||||||
await storage.saveSettings(mockSettings);
|
await storage.saveSettings(mockSettings)
|
||||||
},
|
},
|
||||||
|
|
||||||
// 保存notes到localStorage
|
// 保存notes到localStorage
|
||||||
async saveNotes() {
|
async saveNotes() {
|
||||||
try {
|
try {
|
||||||
await storage.saveNotes(this.notes);
|
await storage.saveNotes(this.notes)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving notes:', error);
|
console.error('Error saving notes:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 保存folders到localStorage
|
// 保存folders到localStorage
|
||||||
async saveFolders() {
|
async saveFolders() {
|
||||||
try {
|
try {
|
||||||
await storage.saveFolders(this.folders);
|
await storage.saveFolders(this.folders)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving folders:', error);
|
console.error('Error saving folders:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 保存settings到localStorage
|
// 保存settings到localStorage
|
||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
try {
|
try {
|
||||||
await storage.saveSettings(this.settings);
|
await storage.saveSettings(this.settings)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving settings:', error);
|
console.error('Error saving settings:', error)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Note functions
|
// Note functions
|
||||||
async addNote(note) {
|
async addNote(note) {
|
||||||
try {
|
try {
|
||||||
const newNote = await storage.addNote(note);
|
const newNote = await storage.addNote(note)
|
||||||
this.notes.push(newNote);
|
this.notes.push(newNote)
|
||||||
return newNote;
|
return newNote
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding note:', error);
|
console.error('Error adding note:', error)
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateNote(id, updates) {
|
async updateNote(id, updates) {
|
||||||
try {
|
try {
|
||||||
const updatedNote = await storage.updateNote(id, updates);
|
const updatedNote = await storage.updateNote(id, updates)
|
||||||
if (updatedNote) {
|
if (updatedNote) {
|
||||||
const index = this.notes.findIndex(note => note.id === id);
|
const index = this.notes.findIndex(note => note.id === id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.notes[index] = updatedNote;
|
this.notes[index] = updatedNote
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return updatedNote;
|
return updatedNote
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating note:', error);
|
console.error('Error updating note:', error)
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteNote(id) {
|
async deleteNote(id) {
|
||||||
try {
|
try {
|
||||||
const result = await storage.deleteNote(id);
|
const result = await storage.deleteNote(id)
|
||||||
if (result) {
|
if (result) {
|
||||||
this.notes = this.notes.filter(note => note.id !== id);
|
this.notes = this.notes.filter(note => note.id !== id)
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting note:', error);
|
console.error('Error deleting note:', error)
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 将便签移至回收站
|
||||||
|
async moveToTrash(id) {
|
||||||
|
try {
|
||||||
|
const updatedNote = await storage.updateNote(id, { isDeleted: true, deletedAt: new Date().toISOString() })
|
||||||
|
if (updatedNote) {
|
||||||
|
const index = this.notes.findIndex(note => note.id === id)
|
||||||
|
if (index !== -1) {
|
||||||
|
this.notes[index] = updatedNote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedNote
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error moving note to trash:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 永久删除便签
|
||||||
|
async permanentlyDeleteNote(id) {
|
||||||
|
try {
|
||||||
|
const result = await storage.deleteNote(id)
|
||||||
|
if (result) {
|
||||||
|
this.notes = this.notes.filter(note => note.id !== id)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error permanently deleting note:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Folder functions
|
// Folder functions
|
||||||
async addFolder(folder) {
|
async addFolder(folder) {
|
||||||
try {
|
try {
|
||||||
const newFolder = await storage.addFolder(folder);
|
const newFolder = await storage.addFolder(folder)
|
||||||
this.folders.push(newFolder);
|
this.folders.push(newFolder)
|
||||||
return newFolder;
|
return newFolder
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error adding folder:', error);
|
console.error('Error adding folder:', error)
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Settings functions
|
// Settings functions
|
||||||
async updateSettings(newSettings) {
|
async updateSettings(newSettings) {
|
||||||
try {
|
try {
|
||||||
const updatedSettings = { ...this.settings, ...newSettings };
|
const updatedSettings = { ...this.settings, ...newSettings }
|
||||||
this.settings = updatedSettings;
|
this.settings = updatedSettings
|
||||||
await storage.saveSettings(updatedSettings);
|
await storage.saveSettings(updatedSettings)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating settings:', error);
|
console.error('Error updating settings:', error)
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 切换云同步设置
|
// 切换云同步设置
|
||||||
async toggleCloudSync() {
|
async toggleCloudSync() {
|
||||||
await this.updateSettings({ cloudSync: !this.settings.cloudSync });
|
await this.updateSettings({ cloudSync: !this.settings.cloudSync })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 切换深色模式设置
|
// 切换深色模式设置
|
||||||
async toggleDarkMode() {
|
async toggleDarkMode() {
|
||||||
await this.updateSettings({ darkMode: !this.settings.darkMode });
|
await this.updateSettings({ darkMode: !this.settings.darkMode })
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
// Smartisan Notes Color Scheme - Based on Original Design
|
|
||||||
export default {
|
|
||||||
// Primary colors - Original Smartisan Notes brown/gold palette
|
|
||||||
primary: '#5c3c2a', // Main brown color for UI elements
|
|
||||||
primaryDark: '#4a3224', // Darker shade of primary
|
|
||||||
primaryLight: '#f5f0e6', // Light background tone
|
|
||||||
|
|
||||||
// Background colors - Warm paper-like tones
|
|
||||||
background: '#fbf7ed', // Main app background - warm off-white
|
|
||||||
backgroundSecondary: '#f7f2e9', // Slightly darker background
|
|
||||||
backgroundCard: '#ffffff', // Pure white for cards/notes
|
|
||||||
searchBarBackground: '#f0f0f0', // Search bar background - light gray
|
|
||||||
|
|
||||||
// Text colors - Brown/black tones for readability
|
|
||||||
textPrimary: '#5c3c2a', // Main text color - dark brown
|
|
||||||
textSecondary: '#6e482f', // Secondary text - medium brown
|
|
||||||
textTertiary: '#9e836c', // Tertiary text - light brown/gray
|
|
||||||
textInverted: '#ffffff', // White text for dark backgrounds
|
|
||||||
|
|
||||||
// Accent colors - Smartisan's signature colors
|
|
||||||
accentBlue: '#5c89f2', // Blue for links/actions
|
|
||||||
accentGreen: '#97cc4e', // Green for success/positive actions
|
|
||||||
accentRed: '#e65c53', // Red for errors/dangerous actions
|
|
||||||
accentOrange: '#f0880d', // Orange for warnings/highlights
|
|
||||||
accentYellow: '#ffd633', // Yellow for starred items/highlights (updated to match original)
|
|
||||||
|
|
||||||
// Note specific colors
|
|
||||||
noteTitle: '#5c3c2a', // Note title color
|
|
||||||
noteContent: '#6e482f', // Note content color
|
|
||||||
noteDate: '#b9a691', // Date/time color
|
|
||||||
noteStar: '#ffd633', // Star/favorite color (updated to match original)
|
|
||||||
|
|
||||||
// Folder colors
|
|
||||||
folderName: '#5c3c2a', // Folder name color
|
|
||||||
folderCount: '#99000000', // Folder item count color (60% black)
|
|
||||||
folderItemSelected: '#f0f0f0', // Folder item selected background color
|
|
||||||
|
|
||||||
// Button colors - Based on Smartisan's button styles
|
|
||||||
buttonPrimary: '#5c3c2a', // Primary button - brown
|
|
||||||
buttonSecondary: '#97cc4e', // Secondary button - green
|
|
||||||
buttonDanger: '#e65c53', // Danger button - red
|
|
||||||
buttonDisabled: '#d4d4d5', // Disabled button - light gray
|
|
||||||
|
|
||||||
// Status colors
|
|
||||||
success: '#79ad31', // Success - green
|
|
||||||
warning: '#f0880d', // Warning - orange
|
|
||||||
error: '#e64746', // Error - red
|
|
||||||
info: '#5c89f2', // Info - blue
|
|
||||||
|
|
||||||
// UI elements - Borders, dividers, shadows
|
|
||||||
border: '#e5ddca', // Light brown border
|
|
||||||
divider: '#e5e5e5', // Light gray divider
|
|
||||||
shadow: '#00000014', // Subtle shadow
|
|
||||||
|
|
||||||
// Transparency variants
|
|
||||||
black05: '#0000000d', // 5% black
|
|
||||||
black10: '#0000001a', // 10% black
|
|
||||||
black20: '#00000033', // 20% black
|
|
||||||
black30: '#0000004d', // 30% black
|
|
||||||
black40: '#00000066', // 40% black
|
|
||||||
black50: '#00000080', // 50% black
|
|
||||||
black60: '#00000099', // 60% black
|
|
||||||
black80: '#000000cc', // 80% black
|
|
||||||
black90: '#000000e6', // 90% black
|
|
||||||
|
|
||||||
white10: '#ffffff1a', // 10% white
|
|
||||||
white20: '#ffffff33', // 20% white
|
|
||||||
white30: '#ffffff4d', // 30% white
|
|
||||||
white40: '#ffffff66', // 40% white
|
|
||||||
white50: '#ffffff80', // 50% white
|
|
||||||
white60: '#ffffff99', // 60% white
|
|
||||||
white80: '#ffffffcc', // 80% white
|
|
||||||
white90: '#ffffffe6', // 90% white
|
|
||||||
};
|
|
||||||
@@ -30,7 +30,9 @@ export const addNote = async (note) => {
|
|||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
isStarred: note.isStarred || false,
|
isStarred: note.isStarred || false,
|
||||||
isTop: note.isTop || false,
|
isTop: note.isTop || false,
|
||||||
hasImage: note.hasImage || false
|
hasImage: note.hasImage || false,
|
||||||
|
isDeleted: note.isDeleted || false,
|
||||||
|
deletedAt: note.deletedAt || null
|
||||||
};
|
};
|
||||||
|
|
||||||
const notes = await getNotes();
|
const notes = await getNotes();
|
||||||
|
|||||||
@@ -1,574 +0,0 @@
|
|||||||
// Styles for Smartisan Notes - Based on React Native version
|
|
||||||
export default {
|
|
||||||
// Common styles - Based on Smartisan Notes design principles
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'var(--background)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Header styles - Warm, minimal design
|
|
||||||
header: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: 12,
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
backgroundColor: 'var(--background)',
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'var(--border)',
|
|
||||||
},
|
|
||||||
|
|
||||||
headerTitleContainer: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
|
|
||||||
headerTitleTouchable: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
paddingVertical: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
headerTitle: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
|
|
||||||
headerFolderArrow: {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
tintColor: 'var(--text-primary)',
|
|
||||||
marginLeft: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
headerButton: {
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
headerButtonText: {
|
|
||||||
fontSize: 16,
|
|
||||||
color: 'var(--primary)',
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
|
|
||||||
headerActionIcon: {
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
tintColor: 'var(--primary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Folder list styles
|
|
||||||
folderListContainer: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 50,
|
|
||||||
left: '10%',
|
|
||||||
right: '10%',
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
borderRadius: 8,
|
|
||||||
shadowColor: 'var(--shadow)',
|
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowOpacity: 0.2,
|
|
||||||
shadowRadius: 4,
|
|
||||||
elevation: 3,
|
|
||||||
zIndex: 100,
|
|
||||||
},
|
|
||||||
|
|
||||||
folderListItem: {
|
|
||||||
paddingVertical: 12,
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'var(--border)',
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
|
|
||||||
folderListItemActive: {
|
|
||||||
backgroundColor: 'var(--folder-item-selected)',
|
|
||||||
},
|
|
||||||
|
|
||||||
folderListItemText: {
|
|
||||||
fontSize: 16,
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
folderListItemTextActive: {
|
|
||||||
color: 'var(--primary)',
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
|
|
||||||
folderListItemCount: {
|
|
||||||
fontSize: 13,
|
|
||||||
color: 'var(--text-tertiary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Note list styles - Clean, paper-like appearance
|
|
||||||
noteListContainer: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'var(--background)',
|
|
||||||
},
|
|
||||||
|
|
||||||
searchContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
paddingVertical: 8,
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'var(--border)',
|
|
||||||
},
|
|
||||||
|
|
||||||
searchInputContainer: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
height: 36,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
paddingVertical: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
searchInputBackground: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
backgroundColor: '#f0f0f0',
|
|
||||||
borderRadius: 4,
|
|
||||||
height: 36,
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
paddingVertical: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
searchInput: {
|
|
||||||
flex: 1,
|
|
||||||
fontSize: 16,
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
marginLeft: 8,
|
|
||||||
marginRight: 8,
|
|
||||||
padding: 0,
|
|
||||||
includeFontPadding: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
searchLeftIcon: {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
tintColor: 'var(--text-tertiary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
searchClearIcon: {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
tintColor: 'var(--text-tertiary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteListEmptyContainer: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 16,
|
|
||||||
backgroundColor: 'var(--background)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteListEmptyText: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: 'var(--text-tertiary)',
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteListEmptySubtext: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: 'var(--text-tertiary)',
|
|
||||||
textAlign: 'center',
|
|
||||||
lineHeight: 20,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteCount: {
|
|
||||||
fontSize: 13,
|
|
||||||
color: 'var(--text-tertiary)',
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
paddingVertical: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Note item styles - Paper note appearance with subtle shadows
|
|
||||||
noteItem: {
|
|
||||||
padding: 0,
|
|
||||||
borderRadius: 6,
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
borderLeftColor: 'transparent',
|
|
||||||
shadowColor: 'var(--shadow)',
|
|
||||||
shadowOffset: { width: 0, height: 1 },
|
|
||||||
shadowOpacity: 0.1,
|
|
||||||
shadowRadius: 2,
|
|
||||||
elevation: 1,
|
|
||||||
overflow: 'hidden',
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteItemDeleteButton: {
|
|
||||||
backgroundColor: 'var(--accent-red)',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: 80,
|
|
||||||
height: '100%',
|
|
||||||
borderRadius: 6,
|
|
||||||
marginBottom: 10,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteItemDeleteButtonImage: {
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
tintColor: 'var(--text-inverted)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteItemHeader: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteItemTitle: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: 'var(--note-title)',
|
|
||||||
flex: 1,
|
|
||||||
marginRight: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteItemStar: {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
tintColor: 'var(--note-star)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteItemContent: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: 'var(--note-content)',
|
|
||||||
marginBottom: 8,
|
|
||||||
lineHeight: 20,
|
|
||||||
includeFontPadding: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteItemDate: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: 'var(--note-date)',
|
|
||||||
includeFontPadding: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Floating action button - Circular button with warm color
|
|
||||||
fab: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 24,
|
|
||||||
right: 24,
|
|
||||||
backgroundColor: 'var(--primary)',
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
borderRadius: 25,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
shadowColor: 'var(--shadow)',
|
|
||||||
shadowOffset: { width: 0, height: 2 },
|
|
||||||
shadowOpacity: 0.2,
|
|
||||||
shadowRadius: 3,
|
|
||||||
elevation: 3,
|
|
||||||
},
|
|
||||||
|
|
||||||
fabIcon: {
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
tintColor: 'var(--text-inverted)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Folder item styles - Clean list items with folder icon
|
|
||||||
folderItem: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingVertical: 14,
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
marginBottom: 1,
|
|
||||||
borderLeftWidth: 3,
|
|
||||||
borderLeftColor: 'var(--accent-orange)',
|
|
||||||
overflow: 'hidden',
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
},
|
|
||||||
|
|
||||||
folderItemIcon: {
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
tintColor: 'var(--folder-name)',
|
|
||||||
},
|
|
||||||
|
|
||||||
folderItemInfo: {
|
|
||||||
flex: 1,
|
|
||||||
marginLeft: 12,
|
|
||||||
},
|
|
||||||
|
|
||||||
folderItemName: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '500',
|
|
||||||
color: 'var(--folder-name)',
|
|
||||||
marginBottom: 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
folderItemCount: {
|
|
||||||
fontSize: 13,
|
|
||||||
color: 'var(--folder-count)',
|
|
||||||
},
|
|
||||||
|
|
||||||
folderItemArrow: {
|
|
||||||
fontSize: 18,
|
|
||||||
color: 'var(--text-tertiary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Note editor styles - Clean writing surface
|
|
||||||
noteEditorContainer: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
},
|
|
||||||
|
|
||||||
editorToolbar: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
paddingVertical: 8,
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'var(--border)',
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
},
|
|
||||||
|
|
||||||
editorToolbarButton: {
|
|
||||||
padding: 8,
|
|
||||||
marginRight: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
editorToolbarIcon: {
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
tintColor: 'var(--text-primary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteEditorContent: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteEditorTitle: {
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: 'var(--note-title)',
|
|
||||||
marginBottom: 16,
|
|
||||||
paddingVertical: 8,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'var(--border)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteEditorContentInput: {
|
|
||||||
fontSize: 16,
|
|
||||||
color: 'var(--note-content)',
|
|
||||||
lineHeight: 24,
|
|
||||||
flex: 1,
|
|
||||||
textAlignVertical: 'top',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Note detail styles - Clean reading experience
|
|
||||||
noteDetailContainer: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailContent: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailHeader: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: 16,
|
|
||||||
paddingVertical: 4,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'var(--border)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailDate: {
|
|
||||||
fontSize: 13,
|
|
||||||
color: 'var(--note-date)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailStarIcon: {
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
tintColor: 'var(--note-star)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailContentText: {
|
|
||||||
fontSize: 16,
|
|
||||||
color: 'var(--note-content)',
|
|
||||||
lineHeight: 24,
|
|
||||||
includeFontPadding: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailFooter: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-around',
|
|
||||||
paddingVertical: 12,
|
|
||||||
backgroundColor: 'var(--background)',
|
|
||||||
borderTopWidth: 1,
|
|
||||||
borderTopColor: 'var(--border)',
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailActionButton: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
paddingVertical: 10,
|
|
||||||
backgroundColor: 'var(--primary)',
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailActionButtonText: {
|
|
||||||
color: 'var(--text-inverted)',
|
|
||||||
fontWeight: '500',
|
|
||||||
fontSize: 15,
|
|
||||||
marginLeft: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
noteDetailActionIcon: {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
tintColor: 'var(--text-inverted)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Settings styles - Clean, organized sections
|
|
||||||
settingsSection: {
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
|
|
||||||
settingsSectionTitle: {
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: 'var(--text-tertiary)',
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
paddingVertical: 10,
|
|
||||||
backgroundColor: 'var(--background-secondary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
settingsItem: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingHorizontal: 16,
|
|
||||||
paddingVertical: 14,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: 'var(--border)',
|
|
||||||
},
|
|
||||||
|
|
||||||
settingsItemWithIcon: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
|
|
||||||
settingsItemIcon: {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
tintColor: 'var(--text-primary)',
|
|
||||||
marginRight: 12,
|
|
||||||
},
|
|
||||||
|
|
||||||
settingsItemText: {
|
|
||||||
fontSize: 16,
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
settingsItemValue: {
|
|
||||||
fontSize: 15,
|
|
||||||
color: 'var(--text-tertiary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Modal styles - Clean dialogs
|
|
||||||
modalContainer: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'var(--black-50)',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
|
|
||||||
modalContent: {
|
|
||||||
backgroundColor: 'var(--background-card)',
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 20,
|
|
||||||
width: '80%',
|
|
||||||
maxWidth: 300,
|
|
||||||
},
|
|
||||||
|
|
||||||
modalTitle: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: '600',
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
marginBottom: 16,
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
|
|
||||||
modalInput: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'var(--border)',
|
|
||||||
borderRadius: 4,
|
|
||||||
padding: 12,
|
|
||||||
fontSize: 16,
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
|
|
||||||
modalButtons: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
|
|
||||||
modalButton: {
|
|
||||||
flex: 1,
|
|
||||||
paddingVertical: 12,
|
|
||||||
alignItems: 'center',
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
|
|
||||||
modalButtonCancel: {
|
|
||||||
backgroundColor: 'var(--background-secondary)',
|
|
||||||
marginRight: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
modalButtonConfirm: {
|
|
||||||
backgroundColor: 'var(--primary)',
|
|
||||||
marginLeft: 8,
|
|
||||||
},
|
|
||||||
|
|
||||||
modalButtonText: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '500',
|
|
||||||
},
|
|
||||||
|
|
||||||
modalButtonTextCancel: {
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
},
|
|
||||||
|
|
||||||
modalButtonTextConfirm: {
|
|
||||||
color: 'var(--text-inverted)',
|
|
||||||
},
|
|
||||||
|
|
||||||
// Overlay style for dismissing folder list
|
|
||||||
overlay: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
zIndex: 99,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Types
|
|
||||||
export const Note = {
|
|
||||||
id: String,
|
|
||||||
title: String,
|
|
||||||
content: String,
|
|
||||||
createdAt: Date,
|
|
||||||
updatedAt: Date,
|
|
||||||
folderId: String,
|
|
||||||
isStarred: Boolean,
|
|
||||||
isTop: Boolean,
|
|
||||||
hasImage: Boolean
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Folder = {
|
|
||||||
id: String,
|
|
||||||
name: String,
|
|
||||||
createdAt: Date
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Settings = {
|
|
||||||
cloudSync: Boolean,
|
|
||||||
darkMode: Boolean
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user