Replace the abandoned RN iOS path with a SwiftUI app (iOS 17+) that reuses the shared TipTap editor bundle, GRDB local mirror/outbox, and Android-aligned shell chrome for list, create, detail, and Me. Package the editor HTML in the app resources so Markdown renders, present edit from the workspace root with a UIKit FAB, and keep caret stable while typing.
120 lines
4.3 KiB
Swift
120 lines
4.3 KiB
Swift
import Foundation
|
|
import GRDB
|
|
|
|
enum AppDatabase {
|
|
static let fileName = "edgeever-ios.sqlite"
|
|
|
|
static func makeShared() throws -> DatabaseQueue {
|
|
let dir = try FileManager.default.url(
|
|
for: .applicationSupportDirectory,
|
|
in: .userDomainMask,
|
|
appropriateFor: nil,
|
|
create: true
|
|
).appendingPathComponent("EdgeEver", isDirectory: true)
|
|
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
let path = dir.appendingPathComponent(fileName).path
|
|
var config = Configuration()
|
|
config.prepareDatabase { db in
|
|
try db.execute(sql: "PRAGMA foreign_keys = ON")
|
|
}
|
|
let dbQueue = try DatabaseQueue(path: path, configuration: config)
|
|
try dbQueue.writeWithoutTransaction { db in
|
|
try db.execute(sql: "PRAGMA journal_mode = WAL")
|
|
}
|
|
try migrator.migrate(dbQueue)
|
|
return dbQueue
|
|
}
|
|
|
|
/// In-memory queue for unit tests.
|
|
static func makeEmpty() throws -> DatabaseQueue {
|
|
let dbQueue = try DatabaseQueue()
|
|
try migrator.migrate(dbQueue)
|
|
return dbQueue
|
|
}
|
|
|
|
private static var migrator: DatabaseMigrator {
|
|
var migrator = DatabaseMigrator()
|
|
migrator.registerMigration("v1_mirror_outbox_drafts") { db in
|
|
try db.execute(sql: """
|
|
CREATE TABLE mobile_notebooks (
|
|
scope TEXT NOT NULL,
|
|
id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
sort_order INTEGER NOT NULL,
|
|
data_json TEXT NOT NULL,
|
|
PRIMARY KEY (scope, id)
|
|
);
|
|
|
|
CREATE TABLE mobile_memos (
|
|
scope TEXT NOT NULL,
|
|
id TEXT NOT NULL,
|
|
notebook_id TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
content_text TEXT NOT NULL,
|
|
tags_text TEXT NOT NULL,
|
|
is_pinned INTEGER NOT NULL,
|
|
is_deleted INTEGER NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
deleted_at TEXT,
|
|
data_json TEXT NOT NULL,
|
|
PRIMARY KEY (scope, id)
|
|
);
|
|
|
|
CREATE INDEX idx_mobile_memos_feed
|
|
ON mobile_memos(scope, is_deleted, updated_at DESC);
|
|
CREATE INDEX idx_mobile_memos_notebook
|
|
ON mobile_memos(scope, notebook_id, is_deleted, updated_at DESC);
|
|
|
|
CREATE TABLE mobile_sync_meta (
|
|
scope TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
value TEXT NOT NULL,
|
|
PRIMARY KEY (scope, key)
|
|
);
|
|
|
|
CREATE TABLE mobile_id_mappings (
|
|
scope TEXT NOT NULL,
|
|
temporary_id TEXT NOT NULL,
|
|
remote_id TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
PRIMARY KEY (scope, temporary_id)
|
|
);
|
|
|
|
CREATE TABLE mobile_sync_outbox (
|
|
scope TEXT NOT NULL,
|
|
id TEXT NOT NULL,
|
|
kind TEXT NOT NULL,
|
|
memo_id TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
payload_json TEXT NOT NULL,
|
|
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
last_error TEXT,
|
|
next_attempt_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
version INTEGER NOT NULL DEFAULT 1,
|
|
PRIMARY KEY (scope, id)
|
|
);
|
|
|
|
CREATE INDEX idx_mobile_outbox_flush
|
|
ON mobile_sync_outbox(scope, status, next_attempt_at, created_at);
|
|
|
|
CREATE TABLE mobile_drafts (
|
|
scope TEXT NOT NULL,
|
|
draft_key TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
content_markdown TEXT NOT NULL,
|
|
content_json TEXT,
|
|
notebook_id TEXT NOT NULL,
|
|
tags_text TEXT NOT NULL,
|
|
expected_revision INTEGER,
|
|
updated_at TEXT NOT NULL,
|
|
PRIMARY KEY (scope, draft_key)
|
|
);
|
|
""")
|
|
}
|
|
return migrator
|
|
}
|
|
}
|