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 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-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>
|
||||
<style>
|
||||
/* Smartisan Notes Color Scheme - Based on Original Design */
|
||||
@@ -15,8 +18,8 @@
|
||||
--primary-light: #f5f0e6; /* Light background tone */
|
||||
|
||||
/* Editor typography - Consistent font size and line height */
|
||||
--editor-font-size: 19px; /* Base font size for editor */
|
||||
--editor-line-height: 1.5; /* Line height for editor */
|
||||
--editor-font-size: 23px; /* Base font size for editor */
|
||||
--editor-line-height: 1.1; /* Line height for editor */
|
||||
|
||||
/* Background colors - Warm paper-like tones */
|
||||
--background: #fbf7ed; /* Main app background - warm off-white */
|
||||
@@ -92,7 +95,6 @@
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
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);
|
||||
/* 适配iPhone X及更新机型的刘海屏 */
|
||||
padding-top: env(safe-area-inset-top);
|
||||
|
||||
@@ -8,6 +8,7 @@ html {
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<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" />
|
||||
</div>
|
||||
@@ -32,10 +32,6 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
@@ -251,6 +247,12 @@ const handleTouchEnd = () => {
|
||||
color: #816d61;
|
||||
font-size: 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 {
|
||||
width: 1.06rem;
|
||||
|
||||
@@ -28,6 +28,26 @@ const editorRef = ref(null)
|
||||
const content = ref(props.modelValue || '')
|
||||
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([
|
||||
{
|
||||
@@ -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 selection = window.getSelection()
|
||||
@@ -550,14 +579,6 @@ const insertImage = () => {
|
||||
fileInput.click()
|
||||
}
|
||||
|
||||
// 处理输入事件
|
||||
const handleInput = () => {
|
||||
if (editorRef.value) {
|
||||
content.value = editorRef.value.innerHTML
|
||||
emit('update:modelValue', content.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理键盘事件
|
||||
const handleKeydown = e => {
|
||||
// 处理Shift+Enter键换行
|
||||
@@ -710,15 +731,42 @@ const handleToolbarFocusOut = () => {
|
||||
}, 200) // 增加延迟时间,确保有足够时间处理点击事件
|
||||
}
|
||||
|
||||
// 监听外部值变化
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
getContent: () => content.value,
|
||||
setContent: newContent => {
|
||||
content.value = newContent
|
||||
console.log('Setting content in editor:', newContent)
|
||||
content.value = newContent || ''
|
||||
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>
|
||||
|
||||
@@ -914,31 +962,7 @@ defineExpose({
|
||||
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 {
|
||||
max-width: 100%;
|
||||
@@ -972,71 +996,3 @@ defineExpose({
|
||||
margin: 0;
|
||||
}
|
||||
</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>
|
||||
<ion-page>
|
||||
<Header
|
||||
title="文件夹"
|
||||
:onBack="() => window.history.back()"
|
||||
/>
|
||||
<div style="padding: 10px; background-color: var(--background)">
|
||||
<div style="display: flex; align-items: center; background-color: var(--search-bar-background); border-radius: 8px; padding: 0 10px">
|
||||
<ion-icon :icon="search" style="font-size: 20px; color: var(--text-tertiary)"></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>
|
||||
<Header title="文件夹" :onBack="() => window.history.back()" />
|
||||
<div class="folder-page-container">
|
||||
<div class="search-container">
|
||||
<ion-icon :icon="search" class="search-icon"></ion-icon>
|
||||
<ion-input placeholder="搜索文件夹..." :value="searchQuery" @ionChange="e => setSearchQuery(e.detail.value)" class="search-input"></ion-input>
|
||||
<ion-button v-if="searchQuery.length > 0" fill="clear" @click="() => setSearchQuery('')">
|
||||
<ion-icon :icon="closeCircle" class="clear-icon"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
<ion-content>
|
||||
<ion-list style="background-color: var(--background); padding: 0 16px; --ion-item-background: var(--background)">
|
||||
<ion-list class="folder-list">
|
||||
<FolderManage
|
||||
:allCount="allNotesCount"
|
||||
:starredCount="starredNotesCount"
|
||||
@@ -33,47 +21,46 @@
|
||||
:onAllClick="() => handleFolderPress('all')"
|
||||
:onStarredClick="() => handleFolderPress('starred')"
|
||||
:onTrashClick="() => handleFolderPress('trash')"
|
||||
:onArchiveClick="() => handleFolderPress('archive')"
|
||||
/>
|
||||
:onArchiveClick="() => handleFolderPress('archive')" />
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useAppStore } from '../stores/useAppStore';
|
||||
import { search, closeCircle } from 'ionicons/icons';
|
||||
import FolderManage from '../components/FolderManage.vue';
|
||||
import Header from '../components/Header.vue';
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import { search, closeCircle } from 'ionicons/icons'
|
||||
import FolderManage from '../components/FolderManage.vue'
|
||||
import Header from '../components/Header.vue'
|
||||
|
||||
const store = useAppStore();
|
||||
const store = useAppStore()
|
||||
|
||||
// 加载初始数据
|
||||
onMounted(() => {
|
||||
store.loadData();
|
||||
});
|
||||
store.loadData()
|
||||
})
|
||||
|
||||
const searchQuery = ref('');
|
||||
const selectedFolder = ref('all');
|
||||
const searchQuery = ref('')
|
||||
const selectedFolder = ref('all')
|
||||
|
||||
// Calculate note count for each folder
|
||||
const foldersWithCount = computed(() => {
|
||||
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 {
|
||||
...folder,
|
||||
noteCount,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Add default folders at the beginning
|
||||
const allNotesCount = computed(() => store.notes.length);
|
||||
const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length);
|
||||
const allNotesCount = computed(() => store.notes.length)
|
||||
const starredNotesCount = computed(() => store.notes.filter(note => note.isStarred).length)
|
||||
// Assuming we have a way to track deleted notes in the future
|
||||
const trashNotesCount = 0;
|
||||
const archiveCount = 0;
|
||||
const trashNotesCount = 0
|
||||
const archiveCount = 0
|
||||
|
||||
const foldersWithAllNotes = computed(() => {
|
||||
return [
|
||||
@@ -81,39 +68,76 @@ const foldersWithAllNotes = computed(() => {
|
||||
{ id: 'starred', name: '加星便签', noteCount: starredNotesCount.value, createdAt: new Date() },
|
||||
{ id: 'trash', name: '回收站', noteCount: trashNotesCount, createdAt: new Date() },
|
||||
...foldersWithCount.value,
|
||||
];
|
||||
});
|
||||
]
|
||||
})
|
||||
|
||||
const handleFolderPress = (folderId) => {
|
||||
const handleFolderPress = folderId => {
|
||||
// 更新选中的文件夹状态
|
||||
selectedFolder.value = folderId;
|
||||
selectedFolder.value = folderId
|
||||
// 在实际应用中,这里会将选中的文件夹传递回NoteListScreen
|
||||
// 通过导航参数传递选中的文件夹ID
|
||||
window.location.hash = `#/notes?folder=${folderId}`;
|
||||
};
|
||||
window.location.hash = `#/notes?folder=${folderId}`
|
||||
}
|
||||
|
||||
const handleAddFolder = () => {
|
||||
// In a full implementation, this would open a folder creation dialog
|
||||
console.log('Add folder pressed');
|
||||
};
|
||||
console.log('Add folder pressed')
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
// 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 = () => {
|
||||
window.history.back();
|
||||
};
|
||||
window.history.back()
|
||||
}
|
||||
|
||||
// Filter folders based on search query
|
||||
const filteredFolders = computed(() => {
|
||||
return foldersWithAllNotes.value.filter(folder =>
|
||||
folder.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
);
|
||||
});
|
||||
return foldersWithAllNotes.value.filter(folder => folder.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||
})
|
||||
|
||||
const setSearchQuery = (value) => {
|
||||
searchQuery.value = value;
|
||||
};
|
||||
</script>
|
||||
const setSearchQuery = value => {
|
||||
searchQuery.value = value
|
||||
}
|
||||
</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" />
|
||||
|
||||
<!-- 顶部信息栏 -->
|
||||
<div class="header-info" v-if="isEditing">
|
||||
<div class="header-info">
|
||||
<span class="edit-time">{{ formattedTime }}</span>
|
||||
<span>|</span>
|
||||
<span class="word-count">{{ wordCount }}</span>
|
||||
@@ -12,7 +12,11 @@
|
||||
|
||||
<!-- 富文本编辑器 -->
|
||||
<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>
|
||||
|
||||
<ion-alert
|
||||
@@ -35,34 +39,123 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, nextTick, watch } from 'vue'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import Header from '../components/Header.vue'
|
||||
import RichTextEditor from '../components/RichTextEditor.vue'
|
||||
|
||||
const props = defineProps({
|
||||
noteId: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
// 为了保持向后兼容性,我们也支持noteId属性
|
||||
const noteId = computed(() => props.id || props.noteId)
|
||||
|
||||
const store = useAppStore()
|
||||
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 () => {
|
||||
console.log('NoteEditorPage mounted')
|
||||
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
|
||||
const isEditing = !!props.noteId
|
||||
const existingNote = isEditing ? store.notes.find(n => n.id === props.noteId) : null
|
||||
const isEditing = !!noteId.value
|
||||
const existingNote = isEditing ? store.notes.find(n => n.id === noteId.value) : null
|
||||
|
||||
// Initialize state with existing note data or empty strings
|
||||
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 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 now = new Date()
|
||||
@@ -93,15 +186,18 @@ const wordCount = computed(() => {
|
||||
// 处理保存
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 获取编辑器中的实际内容
|
||||
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
|
||||
|
||||
if (isEditing && existingNote) {
|
||||
// Update existing note
|
||||
await store.updateNote(props.noteId, {
|
||||
content: content.value,
|
||||
await store.updateNote(noteId.value, {
|
||||
content: editorContent,
|
||||
})
|
||||
} else {
|
||||
// Create new note
|
||||
await store.addNote({
|
||||
content: content.value,
|
||||
content: editorContent,
|
||||
isStarred: false,
|
||||
})
|
||||
}
|
||||
@@ -129,9 +225,12 @@ const handleCancel = () => {
|
||||
// 处理创建(用于新建便签)
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
// 获取编辑器中的实际内容
|
||||
const editorContent = editorRef.value ? editorRef.value.getContent() : content.value
|
||||
|
||||
// Create new note
|
||||
await store.addNote({
|
||||
content: content.value,
|
||||
content: editorContent,
|
||||
isStarred: false,
|
||||
})
|
||||
|
||||
@@ -185,7 +284,7 @@ const setShowAlert = value => {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
padding: 8px 16px;
|
||||
padding: 1.5rem 16px 0.7rem 16px;
|
||||
background-color: var(--background-card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 0.7rem;
|
||||
|
||||
@@ -6,41 +6,26 @@
|
||||
<!-- 悬浮文件夹列表 - 使用绝对定位实现 -->
|
||||
<div
|
||||
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
|
||||
:allCount="notes.length"
|
||||
:allCount="allNotesCount"
|
||||
:starredCount="starredNotesCount"
|
||||
:trashCount="0"
|
||||
:trashCount="trashNotesCount"
|
||||
:archiveCount="0"
|
||||
:selectedFolder="currentFolder"
|
||||
:onAllClick="
|
||||
() => {
|
||||
setCurrentFolder('all')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
"
|
||||
:onStarredClick="
|
||||
() => {
|
||||
setCurrentFolder('starred')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
"
|
||||
:onTrashClick="
|
||||
() => {
|
||||
setCurrentFolder('trash')
|
||||
setIsFolderExpanded(false)
|
||||
}
|
||||
" />
|
||||
:onAllClick="handleAllNotesClick"
|
||||
:onStarredClick="handleStarredNotesClick"
|
||||
:onTrashClick="handleTrashNotesClick" />
|
||||
</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" />
|
||||
</div>
|
||||
|
||||
<div style="flex: 1">
|
||||
<div v-for="note in filteredAndSortedNotes" :key="note.id" style="margin: 0.4rem 0">
|
||||
<div class="notes-container">
|
||||
<div v-for="note in filteredAndSortedNotes" :key="note.id" class="note-item">
|
||||
<NoteItem
|
||||
:title="note.title"
|
||||
:content="note.content"
|
||||
@@ -76,7 +61,6 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useAppStore } from '../stores/useAppStore'
|
||||
import { create, settings } from 'ionicons/icons'
|
||||
import NoteItem from '../components/NoteItem.vue'
|
||||
import Header from '../components/Header.vue'
|
||||
import FolderManage from '../components/FolderManage.vue'
|
||||
@@ -102,50 +86,67 @@ const currentFolder = ref('all') // 默认文件夹是"全部便签"
|
||||
const showAlert = ref(false)
|
||||
const noteToDelete = ref(null)
|
||||
|
||||
// 计算加星便签数量
|
||||
// 计算加星便签数量(未删除的)
|
||||
const starredNotesCount = computed(() => {
|
||||
return store.notes.filter(note => note.isStarred).length
|
||||
return store.notes.filter(note => note.isStarred && !note.isDeleted).length
|
||||
})
|
||||
|
||||
// 计算置顶便签数量
|
||||
const topNotesCount = computed(() => {
|
||||
return filteredAndSortedNotes.value.filter(note => note.isTop).length
|
||||
// 计算回收站便签数量
|
||||
const trashNotesCount = computed(() => {
|
||||
return store.notes.filter(note => note.isDeleted).length
|
||||
})
|
||||
|
||||
// 根据当前文件夹过滤便签
|
||||
const filteredNotes = computed(() => {
|
||||
// 预处理搜索查询,提高性能
|
||||
const lowerCaseQuery = searchQuery.value.toLowerCase().trim()
|
||||
|
||||
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) {
|
||||
case 'all':
|
||||
return true
|
||||
// 全部便签中不显示已删除的便签
|
||||
return !note.isDeleted
|
||||
case 'starred':
|
||||
return note.isStarred
|
||||
// 加星便签中只显示未删除的加星便签
|
||||
return note.isStarred && !note.isDeleted
|
||||
case 'trash':
|
||||
// 假设我们有一个isDeleted属性来标识已删除的便签
|
||||
return note.isDeleted || false
|
||||
// 回收站中只显示已删除的便签
|
||||
return note.isDeleted
|
||||
default:
|
||||
return note.folderId === currentFolder.value
|
||||
// 自定义文件夹中不显示已删除的便签
|
||||
return note.folderId === currentFolder.value && !note.isDeleted
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Filter and sort notes
|
||||
const filteredAndSortedNotes = computed(() => {
|
||||
return filteredNotes.value
|
||||
.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
|
||||
return [...filteredNotes.value].sort((a, b) => {
|
||||
// 置顶的便签排在前面
|
||||
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)
|
||||
} else if (sortBy.value === 'starred') {
|
||||
case 'starred':
|
||||
// 加星的便签排在前面
|
||||
return (b.isStarred ? 1 : 0) - (a.isStarred ? 1 : 0)
|
||||
} else {
|
||||
case 'date':
|
||||
default:
|
||||
// 按更新时间倒序排列(最新的在前)
|
||||
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 计算头部标题
|
||||
@@ -158,10 +159,15 @@ const headerTitle = computed(() => {
|
||||
case 'trash':
|
||||
return '回收站'
|
||||
default:
|
||||
return '文件夹'
|
||||
return '全部便签'
|
||||
}
|
||||
})
|
||||
|
||||
// 计算全部便签数量(未删除的)
|
||||
const allNotesCount = computed(() => {
|
||||
return store.notes.filter(note => !note.isDeleted).length
|
||||
})
|
||||
|
||||
const handleNotePress = noteId => {
|
||||
// 导航到编辑页面的逻辑将在路由中处理
|
||||
window.location.hash = `#/editor/${noteId}`
|
||||
@@ -187,21 +193,44 @@ const handleDeleteNote = noteId => {
|
||||
const handleStarToggle = async noteId => {
|
||||
const note = store.notes.find(n => n.id === noteId)
|
||||
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 note = store.notes.find(n => n.id === noteId)
|
||||
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) {
|
||||
store.deleteNote(noteToDelete.value)
|
||||
noteToDelete.value = null
|
||||
try {
|
||||
// 检查当前是否在回收站中
|
||||
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
|
||||
}
|
||||
@@ -215,6 +244,21 @@ const handleSort = () => {
|
||||
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 = () => {
|
||||
// 导航到文件夹页面的逻辑将在路由中处理
|
||||
window.location.hash = '#/folders'
|
||||
@@ -228,29 +272,77 @@ const handleSettingsPress = () => {
|
||||
const handleFolderToggle = () => {
|
||||
// 在实际应用中,这里会触发文件夹列表的展开/收起
|
||||
isFolderExpanded.value = !isFolderExpanded.value
|
||||
console.log('Folder expanded:', !isFolderExpanded.value)
|
||||
}
|
||||
|
||||
const handleSearch = query => {
|
||||
// 搜索功能已在computed属性filteredAndSortedNotes中实现
|
||||
console.log('Search for:', query)
|
||||
|
||||
// 可以在这里添加搜索统计或其它功能
|
||||
if (query && query.length > 0) {
|
||||
console.log(`Found ${filteredAndSortedNotes.value.length} matching notes`)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClearSearch = () => {
|
||||
// 清除搜索已在v-model中处理
|
||||
console.log('Search cleared')
|
||||
|
||||
// 清除搜索后可以重置一些状态
|
||||
setSearchQuery('')
|
||||
}
|
||||
|
||||
const handleSearchFocus = () => {
|
||||
console.log('Search bar focused')
|
||||
// 可以在这里添加获得焦点时的特殊处理
|
||||
}
|
||||
|
||||
const handleSearchBlur = () => {
|
||||
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 => {
|
||||
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 => {
|
||||
@@ -278,4 +370,39 @@ const notes = computed(() => store.notes)
|
||||
background: url(/assets/icons/drawable-xxhdpi/note_background.png);
|
||||
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"
|
||||
/>
|
||||
|
||||
<ion-content style="background-color: var(--background)">
|
||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
||||
<ion-content class="settings-content">
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
账户
|
||||
</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 style="font-size: 16px; color: var(--text-primary)">登录云同步</div>
|
||||
<div style="font-size: 15px; color: var(--text-tertiary)">未登录</div>
|
||||
<div button @click="handleLogin" class="settings-item settings-item-clickable">
|
||||
<div class="item-text-primary">登录云同步</div>
|
||||
<div class="item-text-tertiary">未登录</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
偏好设置
|
||||
</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 style="font-size: 16px; color: var(--text-primary)">云同步</div>
|
||||
<div class="settings-item settings-item-border">
|
||||
<div class="item-text-primary">云同步</div>
|
||||
<ion-toggle
|
||||
slot="end"
|
||||
:checked="settings.cloudSync"
|
||||
@ion-change="toggleCloudSync"
|
||||
></ion-toggle>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; background-color: var(--background-card); padding: 14px 16px">
|
||||
<div style="font-size: 16px; color: var(--text-primary)">深色模式</div>
|
||||
<div class="settings-item">
|
||||
<div class="item-text-primary">深色模式</div>
|
||||
<ion-toggle
|
||||
slot="end"
|
||||
:checked="settings.darkMode"
|
||||
@@ -38,41 +38,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
数据管理
|
||||
</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">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_save_pic.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
||||
<div style="font-size: 16px; color: var(--text-primary)">备份便签</div>
|
||||
<div button @click="handleBackup" class="settings-item settings-item-clickable settings-item-border">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_save_pic.png'" class="item-icon" />
|
||||
<div class="item-text-primary">备份便签</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">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_restore.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
||||
<div style="font-size: 16px; color: var(--text-primary)">恢复便签</div>
|
||||
<div button @click="handleRestore" class="settings-item settings-item-clickable settings-item-border">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_restore.png'" class="item-icon" />
|
||||
<div class="item-text-primary">恢复便签</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">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_share.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
||||
<div style="font-size: 16px; color: var(--text-primary)">导出便签</div>
|
||||
<div button @click="handleExport" class="settings-item settings-item-clickable settings-item-border">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_share.png'" class="item-icon" />
|
||||
<div class="item-text-primary">导出便签</div>
|
||||
</div>
|
||||
<div button @click="handleImport" style="display: flex; align-items: center; background-color: var(--background-card); padding: 14px 16px; cursor: pointer">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_load_error.png'" style="width: 20px; height: 20px; color: var(--text-primary); margin-right: 12px" />
|
||||
<div style="font-size: 16px; color: var(--text-primary)">导入便签</div>
|
||||
<div button @click="handleImport" class="settings-item settings-item-clickable">
|
||||
<img :src="'/assets/icons/drawable-xxhdpi/btn_load_error.png'" class="item-icon" />
|
||||
<div class="item-text-primary">导入便签</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 12px; background-color: var(--background-card)">
|
||||
<div style="background-color: var(--background-secondary); font-size: 13px; font-weight: 600; color: var(--text-tertiary); padding: 10px 16px">
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
关于
|
||||
</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 style="font-size: 16px; color: var(--text-primary)">版本</div>
|
||||
<div style="font-size: 15px; color: var(--text-tertiary)">1.0.0</div>
|
||||
<div class="settings-item settings-item-border">
|
||||
<div class="item-text-primary">版本</div>
|
||||
<div class="item-text-tertiary">1.0.0</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 style="font-size: 16px; color: var(--text-primary)">隐私政策</div>
|
||||
<div button @click="handlePrivacyPolicy" class="settings-item settings-item-clickable settings-item-border">
|
||||
<div class="item-text-primary">隐私政策</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 style="font-size: 16px; color: var(--text-primary)">服务条款</div>
|
||||
<div button @click="handleTermsOfService" class="settings-item settings-item-clickable">
|
||||
<div class="item-text-primary">服务条款</div>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -139,4 +139,65 @@ const handleBackPress = () => {
|
||||
};
|
||||
|
||||
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 * as storage from '../utils/storage';
|
||||
import { defineStore } from 'pinia'
|
||||
import * as storage from '../utils/storage'
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: () => ({
|
||||
notes: [],
|
||||
folders: [],
|
||||
settings: { cloudSync: false, darkMode: false }
|
||||
settings: { cloudSync: false, darkMode: false },
|
||||
}),
|
||||
|
||||
|
||||
getters: {
|
||||
starredNotesCount: (state) => {
|
||||
return state.notes.filter(note => note.isStarred).length;
|
||||
starredNotesCount: state => {
|
||||
return state.notes.filter(note => note.isStarred).length
|
||||
},
|
||||
|
||||
allNotesCount: state => {
|
||||
return state.notes.length
|
||||
},
|
||||
|
||||
allNotesCount: (state) => {
|
||||
return state.notes.length;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
actions: {
|
||||
// 初始化数据
|
||||
async loadData() {
|
||||
try {
|
||||
const loadedNotes = await storage.getNotes();
|
||||
const loadedFolders = await storage.getFolders();
|
||||
const loadedSettings = await storage.getSettings();
|
||||
|
||||
const loadedNotes = await storage.getNotes()
|
||||
const loadedFolders = await storage.getFolders()
|
||||
const loadedSettings = await storage.getSettings()
|
||||
|
||||
// 如果没有数据,则加载mock数据
|
||||
if (loadedNotes.length === 0 && loadedFolders.length === 0) {
|
||||
this.loadMockData();
|
||||
this.loadMockData()
|
||||
} else {
|
||||
this.notes = loadedNotes;
|
||||
this.folders = loadedFolders;
|
||||
this.settings = loadedSettings;
|
||||
this.notes = loadedNotes
|
||||
this.folders = loadedFolders
|
||||
this.settings = loadedSettings
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error);
|
||||
console.error('Error loading data:', error)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// 加载mock数据
|
||||
async loadMockData() {
|
||||
// Mock notes
|
||||
@@ -52,7 +52,9 @@ export const useAppStore = defineStore('app', {
|
||||
folderId: null,
|
||||
isStarred: true,
|
||||
isTop: true,
|
||||
hasImage: false
|
||||
hasImage: false,
|
||||
isDeleted: false,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
@@ -63,7 +65,9 @@ export const useAppStore = defineStore('app', {
|
||||
folderId: null,
|
||||
isStarred: true,
|
||||
isTop: false,
|
||||
hasImage: true
|
||||
hasImage: true,
|
||||
isDeleted: false,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
@@ -74,7 +78,9 @@ export const useAppStore = defineStore('app', {
|
||||
folderId: null,
|
||||
isStarred: false,
|
||||
isTop: false,
|
||||
hasImage: false
|
||||
hasImage: false,
|
||||
isDeleted: false,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
@@ -85,7 +91,9 @@ export const useAppStore = defineStore('app', {
|
||||
folderId: null,
|
||||
isStarred: false,
|
||||
isTop: false,
|
||||
hasImage: false
|
||||
hasImage: false,
|
||||
isDeleted: false,
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
@@ -96,145 +104,191 @@ export const useAppStore = defineStore('app', {
|
||||
folderId: null,
|
||||
isStarred: 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
|
||||
const mockFolders = [
|
||||
{
|
||||
id: 'folder1',
|
||||
name: '工作',
|
||||
createdAt: new Date().toISOString()
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'folder2',
|
||||
name: '个人',
|
||||
createdAt: new Date().toISOString()
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'folder3',
|
||||
name: '学习',
|
||||
createdAt: new Date().toISOString()
|
||||
}
|
||||
];
|
||||
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
]
|
||||
|
||||
// Mock settings
|
||||
const mockSettings = {
|
||||
cloudSync: false,
|
||||
darkMode: false
|
||||
};
|
||||
|
||||
this.notes = mockNotes;
|
||||
this.folders = mockFolders;
|
||||
this.settings = mockSettings;
|
||||
|
||||
darkMode: false,
|
||||
}
|
||||
|
||||
this.notes = mockNotes
|
||||
this.folders = mockFolders
|
||||
this.settings = mockSettings
|
||||
|
||||
// 保存到localStorage
|
||||
await storage.saveNotes(mockNotes);
|
||||
await storage.saveFolders(mockFolders);
|
||||
await storage.saveSettings(mockSettings);
|
||||
await storage.saveNotes(mockNotes)
|
||||
await storage.saveFolders(mockFolders)
|
||||
await storage.saveSettings(mockSettings)
|
||||
},
|
||||
|
||||
|
||||
// 保存notes到localStorage
|
||||
async saveNotes() {
|
||||
try {
|
||||
await storage.saveNotes(this.notes);
|
||||
await storage.saveNotes(this.notes)
|
||||
} catch (error) {
|
||||
console.error('Error saving notes:', error);
|
||||
console.error('Error saving notes:', error)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// 保存folders到localStorage
|
||||
async saveFolders() {
|
||||
try {
|
||||
await storage.saveFolders(this.folders);
|
||||
await storage.saveFolders(this.folders)
|
||||
} catch (error) {
|
||||
console.error('Error saving folders:', error);
|
||||
console.error('Error saving folders:', error)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// 保存settings到localStorage
|
||||
async saveSettings() {
|
||||
try {
|
||||
await storage.saveSettings(this.settings);
|
||||
await storage.saveSettings(this.settings)
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error);
|
||||
console.error('Error saving settings:', error)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Note functions
|
||||
async addNote(note) {
|
||||
try {
|
||||
const newNote = await storage.addNote(note);
|
||||
this.notes.push(newNote);
|
||||
return newNote;
|
||||
const newNote = await storage.addNote(note)
|
||||
this.notes.push(newNote)
|
||||
return newNote
|
||||
} catch (error) {
|
||||
console.error('Error adding note:', error);
|
||||
throw error;
|
||||
console.error('Error adding note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async updateNote(id, updates) {
|
||||
try {
|
||||
const updatedNote = await storage.updateNote(id, updates);
|
||||
const updatedNote = await storage.updateNote(id, updates)
|
||||
if (updatedNote) {
|
||||
const index = this.notes.findIndex(note => note.id === id);
|
||||
const index = this.notes.findIndex(note => note.id === id)
|
||||
if (index !== -1) {
|
||||
this.notes[index] = updatedNote;
|
||||
this.notes[index] = updatedNote
|
||||
}
|
||||
}
|
||||
return updatedNote;
|
||||
return updatedNote
|
||||
} catch (error) {
|
||||
console.error('Error updating note:', error);
|
||||
throw error;
|
||||
console.error('Error updating note:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async deleteNote(id) {
|
||||
try {
|
||||
const result = await storage.deleteNote(id);
|
||||
const result = await storage.deleteNote(id)
|
||||
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) {
|
||||
console.error('Error deleting note:', error);
|
||||
throw error;
|
||||
console.error('Error deleting note:', 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
|
||||
async addFolder(folder) {
|
||||
try {
|
||||
const newFolder = await storage.addFolder(folder);
|
||||
this.folders.push(newFolder);
|
||||
return newFolder;
|
||||
const newFolder = await storage.addFolder(folder)
|
||||
this.folders.push(newFolder)
|
||||
return newFolder
|
||||
} catch (error) {
|
||||
console.error('Error adding folder:', error);
|
||||
throw error;
|
||||
console.error('Error adding folder:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Settings functions
|
||||
async updateSettings(newSettings) {
|
||||
try {
|
||||
const updatedSettings = { ...this.settings, ...newSettings };
|
||||
this.settings = updatedSettings;
|
||||
await storage.saveSettings(updatedSettings);
|
||||
const updatedSettings = { ...this.settings, ...newSettings }
|
||||
this.settings = updatedSettings
|
||||
await storage.saveSettings(updatedSettings)
|
||||
} catch (error) {
|
||||
console.error('Error updating settings:', error);
|
||||
throw error;
|
||||
console.error('Error updating settings:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// 切换云同步设置
|
||||
async toggleCloudSync() {
|
||||
await this.updateSettings({ cloudSync: !this.settings.cloudSync });
|
||||
await this.updateSettings({ cloudSync: !this.settings.cloudSync })
|
||||
},
|
||||
|
||||
|
||||
// 切换深色模式设置
|
||||
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(),
|
||||
isStarred: note.isStarred || 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();
|
||||
|
||||
@@ -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