336 lines
11 KiB
Swift
336 lines
11 KiB
Swift
import Foundation
|
||
|
||
// MARK: - 笔记类型枚举
|
||
enum NoteType: String, Codable {
|
||
case highlight = "highlight"
|
||
case thought = "thought"
|
||
case comment = "comment" // 未来扩展
|
||
}
|
||
|
||
// MARK: - 系统用户ID常量
|
||
extension Note {
|
||
/// 系统用户ID(用于系统笔记)
|
||
static let systemUserId = "00000000-0000-0000-0000-000000000000"
|
||
|
||
/// 判断是否为系统笔记
|
||
var isSystemNote: Bool {
|
||
return userId == Note.systemUserId
|
||
}
|
||
}
|
||
|
||
// MARK: - ✅ Phase 1: 新增 Notebook 模型
|
||
struct Notebook: Identifiable, Codable, Hashable {
|
||
let id: String
|
||
let userId: String
|
||
let title: String
|
||
let description: String?
|
||
let coverImage: String?
|
||
let noteCount: Int? // 计算字段,可选
|
||
let createdAt: Date
|
||
let updatedAt: Date
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case userId = "user_id"
|
||
case title, description
|
||
case coverImage = "cover_image"
|
||
case noteCount = "note_count"
|
||
case createdAt = "created_at"
|
||
case updatedAt = "updated_at"
|
||
}
|
||
|
||
// 自定义解码,处理日期格式
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
|
||
id = try container.decode(String.self, forKey: .id)
|
||
userId = try container.decode(String.self, forKey: .userId)
|
||
title = try container.decode(String.self, forKey: .title)
|
||
description = try container.decodeIfPresent(String.self, forKey: .description)
|
||
coverImage = try container.decodeIfPresent(String.self, forKey: .coverImage)
|
||
noteCount = try container.decodeIfPresent(Int.self, forKey: .noteCount)
|
||
|
||
// 解析日期
|
||
let createdAtString = try container.decode(String.self, forKey: .createdAt)
|
||
guard let createdAtDate = ISO8601DateFormatters.date(from: createdAtString) else {
|
||
throw DecodingError.dataCorruptedError(
|
||
forKey: .createdAt,
|
||
in: container,
|
||
debugDescription: "Invalid date format: \(createdAtString)"
|
||
)
|
||
}
|
||
createdAt = createdAtDate
|
||
|
||
let updatedAtString = try container.decode(String.self, forKey: .updatedAt)
|
||
guard let updatedAtDate = ISO8601DateFormatters.date(from: updatedAtString) else {
|
||
throw DecodingError.dataCorruptedError(
|
||
forKey: .updatedAt,
|
||
in: container,
|
||
debugDescription: "Invalid date format: \(updatedAtString)"
|
||
)
|
||
}
|
||
updatedAt = updatedAtDate
|
||
}
|
||
}
|
||
|
||
// MARK: - ✅ Phase 1: 扩展 Note 模型,支持笔记本和层级结构
|
||
struct Note: Identifiable, Codable {
|
||
let id: String
|
||
let userId: String
|
||
|
||
// ✅ 新增:笔记本和层级字段
|
||
let notebookId: String? // 所属笔记本 ID,nil 表示未分类
|
||
let parentId: String? // 父笔记 ID,nil 表示顶级
|
||
let order: Int // 同级排序,0, 1, 2...
|
||
let level: Int // 层级深度,0=顶级, 1=二级, 2=三级
|
||
|
||
// ✅ 修改:这些字段改为可选(支持独立笔记)
|
||
let courseId: String?
|
||
let nodeId: String?
|
||
let startIndex: Int? // 全局索引(独立笔记为 null)
|
||
let length: Int? // 全局索引(独立笔记为 null)
|
||
|
||
let type: NoteType
|
||
let content: String? // 可选(想法笔记有内容,划线笔记可能为空)
|
||
let quotedText: String? // ✅ 修改:改为可选,独立笔记为 null
|
||
|
||
// ✅ 保留可选字段(用于列表展示,不影响核心功能)
|
||
let userName: String?
|
||
let courseTitle: String?
|
||
let nodeTitle: String?
|
||
let style: String?
|
||
|
||
// ✅ 日期保持为 Date 类型(与现有代码兼容)
|
||
let createdAt: Date
|
||
let updatedAt: Date
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case userId = "user_id"
|
||
case userName = "user_name"
|
||
// ✅ 新增字段
|
||
case notebookId = "notebook_id"
|
||
case parentId = "parent_id"
|
||
case order, level
|
||
// ✅ 修改为可选
|
||
case courseId = "course_id"
|
||
case nodeId = "node_id"
|
||
case startIndex = "start_index"
|
||
case length, type, content
|
||
case quotedText = "quoted_text"
|
||
case style
|
||
case createdAt = "created_at"
|
||
case updatedAt = "updated_at"
|
||
case courseTitle = "course_title"
|
||
case nodeTitle = "node_title"
|
||
}
|
||
|
||
// 自定义解码,处理日期格式
|
||
init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
|
||
id = try container.decode(String.self, forKey: .id)
|
||
userId = try container.decode(String.self, forKey: .userId)
|
||
userName = try container.decodeIfPresent(String.self, forKey: .userName)
|
||
|
||
// ✅ 新增字段:笔记本和层级
|
||
notebookId = try container.decodeIfPresent(String.self, forKey: .notebookId)
|
||
parentId = try container.decodeIfPresent(String.self, forKey: .parentId)
|
||
order = try container.decodeIfPresent(Int.self, forKey: .order) ?? 0
|
||
level = try container.decodeIfPresent(Int.self, forKey: .level) ?? 0
|
||
|
||
// ✅ 修改为可选:支持独立笔记
|
||
courseId = try container.decodeIfPresent(String.self, forKey: .courseId)
|
||
nodeId = try container.decodeIfPresent(String.self, forKey: .nodeId)
|
||
startIndex = try container.decodeIfPresent(Int.self, forKey: .startIndex)
|
||
length = try container.decodeIfPresent(Int.self, forKey: .length)
|
||
quotedText = try container.decodeIfPresent(String.self, forKey: .quotedText)
|
||
|
||
type = try container.decode(NoteType.self, forKey: .type)
|
||
content = try container.decodeIfPresent(String.self, forKey: .content)
|
||
style = try container.decodeIfPresent(String.self, forKey: .style)
|
||
courseTitle = try container.decodeIfPresent(String.self, forKey: .courseTitle)
|
||
nodeTitle = try container.decodeIfPresent(String.self, forKey: .nodeTitle)
|
||
|
||
// 解析日期
|
||
let createdAtString = try container.decode(String.self, forKey: .createdAt)
|
||
guard let createdAtDate = ISO8601DateFormatters.date(from: createdAtString) else {
|
||
throw DecodingError.dataCorruptedError(
|
||
forKey: .createdAt,
|
||
in: container,
|
||
debugDescription: "Invalid date format: \(createdAtString)"
|
||
)
|
||
}
|
||
createdAt = createdAtDate
|
||
|
||
let updatedAtString = try container.decode(String.self, forKey: .updatedAt)
|
||
guard let updatedAtDate = ISO8601DateFormatters.date(from: updatedAtString) else {
|
||
throw DecodingError.dataCorruptedError(
|
||
forKey: .updatedAt,
|
||
in: container,
|
||
debugDescription: "Invalid date format: \(updatedAtString)"
|
||
)
|
||
}
|
||
updatedAt = updatedAtDate
|
||
}
|
||
|
||
// MARK: - 便利初始化器(用于创建临时 Note)
|
||
init(
|
||
id: String,
|
||
userId: String,
|
||
userName: String?,
|
||
notebookId: String? = nil,
|
||
parentId: String? = nil,
|
||
order: Int = 0,
|
||
level: Int = 0,
|
||
courseId: String? = nil,
|
||
nodeId: String? = nil,
|
||
startIndex: Int? = nil,
|
||
length: Int? = nil,
|
||
type: NoteType,
|
||
content: String?,
|
||
quotedText: String? = nil,
|
||
style: String? = nil,
|
||
createdAt: Date,
|
||
updatedAt: Date,
|
||
courseTitle: String? = nil,
|
||
nodeTitle: String? = nil
|
||
) {
|
||
self.id = id
|
||
self.userId = userId
|
||
self.userName = userName
|
||
self.notebookId = notebookId
|
||
self.parentId = parentId
|
||
self.order = order
|
||
self.level = level
|
||
self.courseId = courseId
|
||
self.nodeId = nodeId
|
||
self.startIndex = startIndex
|
||
self.length = length
|
||
self.type = type
|
||
self.content = content
|
||
self.quotedText = quotedText
|
||
self.style = style
|
||
self.createdAt = createdAt
|
||
self.updatedAt = updatedAt
|
||
self.courseTitle = courseTitle
|
||
self.nodeTitle = nodeTitle
|
||
}
|
||
}
|
||
|
||
// MARK: - ✅ Phase 1: Notebook API 响应模型
|
||
struct NotebookListResponse: Codable {
|
||
let success: Bool
|
||
let data: NotebookListData
|
||
}
|
||
|
||
struct NotebookListData: Codable {
|
||
let notebooks: [Notebook]
|
||
let total: Int
|
||
}
|
||
|
||
struct NotebookResponse: Codable {
|
||
let success: Bool
|
||
let data: NotebookData
|
||
}
|
||
|
||
struct NotebookData: Codable {
|
||
let notebook: Notebook
|
||
}
|
||
|
||
// MARK: - API 响应模型
|
||
struct NoteListResponse: Codable {
|
||
let success: Bool
|
||
let data: NoteListData
|
||
}
|
||
|
||
struct NoteListData: Codable {
|
||
let notes: [Note]
|
||
let total: Int
|
||
}
|
||
|
||
struct NoteResponse: Codable {
|
||
let success: Bool
|
||
let data: Note
|
||
}
|
||
|
||
// MARK: - ✅ Phase 1: 创建笔记本请求体
|
||
struct CreateNotebookRequest: Codable {
|
||
let title: String
|
||
let description: String?
|
||
let coverImage: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case title, description
|
||
case coverImage = "cover_image"
|
||
}
|
||
}
|
||
|
||
// MARK: - ✅ Phase 1: 更新笔记本请求体
|
||
struct UpdateNotebookRequest: Codable {
|
||
let title: String?
|
||
let description: String?
|
||
let coverImage: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case title, description
|
||
case coverImage = "cover_image"
|
||
}
|
||
}
|
||
|
||
// MARK: - 创建笔记请求体(✅ Phase 1: 扩展支持笔记本和层级)
|
||
struct CreateNoteRequest: Codable {
|
||
// ✅ 新增:笔记本和层级字段
|
||
let notebookId: String?
|
||
let parentId: String?
|
||
let order: Int?
|
||
let level: Int?
|
||
|
||
// ✅ 修改为可选:支持独立笔记
|
||
let courseId: String?
|
||
let nodeId: String?
|
||
let startIndex: Int? // 可选:全局 NSRange.location
|
||
let length: Int? // 可选:全局 NSRange.length
|
||
|
||
let type: String // "highlight" or "thought"
|
||
let quotedText: String? // ✅ 修改为可选:独立笔记为 null
|
||
let content: String? // 可选(想法笔记有内容,划线笔记可能为空)
|
||
let style: String? // 可选
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case notebookId = "notebook_id"
|
||
case parentId = "parent_id"
|
||
case order, level
|
||
case courseId = "course_id"
|
||
case nodeId = "node_id"
|
||
case startIndex = "start_index"
|
||
case length, type
|
||
case quotedText = "quoted_text"
|
||
case content, style
|
||
}
|
||
}
|
||
|
||
// MARK: - ✅ Phase 1: 更新笔记请求体(扩展支持层级调整)
|
||
struct UpdateNoteRequest: Codable {
|
||
// ✅ 新增:层级调整字段
|
||
let parentId: String?
|
||
let order: Int?
|
||
let level: Int?
|
||
|
||
// 现有字段
|
||
let content: String?
|
||
let quotedText: String?
|
||
let startIndex: Int?
|
||
let length: Int?
|
||
let style: String?
|
||
|
||
enum CodingKeys: String, CodingKey {
|
||
case parentId = "parent_id"
|
||
case order, level
|
||
case content
|
||
case quotedText = "quoted_text"
|
||
case startIndex = "start_index"
|
||
case length, style
|
||
}
|
||
}
|