开始完善便签新建、编辑逻辑

This commit is contained in:
2025-10-12 18:32:25 +08:00
parent 3957a7d3b2
commit 1bb9b4a79e
13 changed files with 696 additions and 1039 deletions

View File

@@ -8,6 +8,7 @@ html {
user-select: none;
-webkit-user-drag: none;
-webkit-tap-highlight-color: transparent;
touch-action: none;
}
body {

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 })
},
},
})

View File

@@ -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
};

View File

@@ -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();

View File

@@ -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,
},
};

View File

@@ -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
};