logseq import plugin
This commit is contained in:
1 parent
3ae84ccdd7
commit
5af49655d6
11 files changed
+301
-193
No files matched your search
@@ -674,132 +674,6 @@ if (str_starts_with($route, '/api/')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Import Logseq vault
|
||||
if ($route === '/api/import-logseq' && $method === 'POST') {
|
||||
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||
http_response_code(400); echo json_encode(['error' => 'no file']); exit;
|
||||
}
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($_FILES['file']['tmp_name']) !== true) {
|
||||
http_response_code(400); echo json_encode(['error' => 'invalid zip']); exit;
|
||||
}
|
||||
|
||||
$uploadDir = __DIR__ . '/uploads';
|
||||
if (!is_dir($uploadDir)) mkdir($uploadDir, 0777, true);
|
||||
|
||||
// Find the vault root (directory containing journals/ or pages/)
|
||||
$prefix = '';
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$name = $zip->getNameIndex($i);
|
||||
if (preg_match('#^(.*/)?journals/#', $name, $m)) { $prefix = $m[1] ?? ''; break; }
|
||||
if (preg_match('#^(.*/)?pages/#', $name, $m)) { $prefix = $m[1] ?? ''; break; }
|
||||
}
|
||||
|
||||
// 1. Copy assets → uploads with name mapping
|
||||
$assetMap = []; // original filename → new hex filename
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$name = $zip->getNameIndex($i);
|
||||
if (!str_starts_with($name, $prefix . 'assets/')) continue;
|
||||
if (str_ends_with($name, '/')) continue;
|
||||
$content = $zip->getFromIndex($i);
|
||||
if ($content === false) continue;
|
||||
$origName = basename($name);
|
||||
$ext = pathinfo($origName, PATHINFO_EXTENSION) ?: 'bin';
|
||||
$newName = bin2hex(random_bytes(16)) . '.' . strtolower($ext);
|
||||
file_put_contents($uploadDir . '/' . $newName, $content);
|
||||
$assetMap[$origName] = $newName;
|
||||
}
|
||||
|
||||
// Helper: rewrite image references in markdown body
|
||||
$rewriteImages = function($body) use ($assetMap) {
|
||||
// Logseq patterns: , , 
|
||||
$body = preg_replace_callback('#!\[([^\]]*)\]\((?:\.\./)?(?:assets/)?([^)]+)\)#', function($m) use ($assetMap) {
|
||||
$file = basename($m[2]);
|
||||
if (isset($assetMap[$file])) return '![' . $m[1] . '](uploads/' . $assetMap[$file] . ')';
|
||||
return $m[0];
|
||||
}, $body);
|
||||
// Logseq block-level image embed: {{embed ((image))}} or just bare refs
|
||||
// Also handle:  with URL-encoded filenames
|
||||
$body = preg_replace_callback('#!\[([^\]]*)\]\((?:\.\./)?assets/([^)]+)\)#', function($m) use ($assetMap) {
|
||||
$file = urldecode(basename($m[2]));
|
||||
if (isset($assetMap[$file])) return '![' . $m[1] . '](uploads/' . $assetMap[$file] . ')';
|
||||
return $m[0];
|
||||
}, $body);
|
||||
return $body;
|
||||
};
|
||||
|
||||
// Helper: clean Logseq markdown (remove bullet prefixes, properties)
|
||||
$cleanBody = function($body) {
|
||||
$lines = explode("\n", $body);
|
||||
$cleaned = [];
|
||||
foreach ($lines as $line) {
|
||||
// Skip Logseq properties (key:: value at start)
|
||||
if (preg_match('/^[a-z_-]+::\s/', $line)) continue;
|
||||
// Remove leading "- " bullet outline format (Logseq default)
|
||||
$line = preg_replace('/^(\t*)- /', '$1', $line);
|
||||
$cleaned[] = $line;
|
||||
}
|
||||
return implode("\n", $cleaned);
|
||||
};
|
||||
|
||||
// Get or create "import" bucket
|
||||
$importBucket = $db->query("SELECT id FROM buckets WHERE name = 'import'")->fetchColumn();
|
||||
if (!$importBucket) {
|
||||
$db->prepare("INSERT INTO buckets (name, position) VALUES ('import', 999)")->execute();
|
||||
$importBucket = $db->lastInsertId();
|
||||
}
|
||||
|
||||
$imported = 0;
|
||||
|
||||
// 2. Import journals → cards with date title
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$name = $zip->getNameIndex($i);
|
||||
if (!str_starts_with($name, $prefix . 'journals/')) continue;
|
||||
if (!str_ends_with($name, '.md')) continue;
|
||||
$content = $zip->getFromIndex($i);
|
||||
if ($content === false || !trim($content)) continue;
|
||||
|
||||
$basename = pathinfo(basename($name), PATHINFO_FILENAME);
|
||||
// 2025_06_18 → 2025-06-18
|
||||
$title = str_replace('_', '-', $basename);
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $title)) continue;
|
||||
|
||||
$body = $rewriteImages($cleanBody($content));
|
||||
// Extract tags
|
||||
preg_match_all('/(?<=\s|^)#([a-zA-Z0-9_\/-]+)/m', $body, $tm);
|
||||
$tags = implode(',', array_unique(array_map('strtolower', $tm[1] ?? [])));
|
||||
|
||||
$db->prepare('INSERT INTO cards (title, body, color, tags, position, bucket_id, created_at) VALUES (?, ?, ?, ?, -1, ?, ?)')
|
||||
->execute([$title, trim($body), '#38bdf8', $tags, $importBucket, $title . ' 00:00:00']);
|
||||
$imported++;
|
||||
}
|
||||
|
||||
// 3. Import pages → cards with page name as title
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$name = $zip->getNameIndex($i);
|
||||
if (!str_starts_with($name, $prefix . 'pages/')) continue;
|
||||
if (!str_ends_with($name, '.md')) continue;
|
||||
$content = $zip->getFromIndex($i);
|
||||
if ($content === false || !trim($content)) continue;
|
||||
|
||||
$title = pathinfo(basename($name), PATHINFO_FILENAME);
|
||||
// Skip Logseq internal pages
|
||||
if (in_array(strtolower($title), ['contents', 'favorites'])) continue;
|
||||
|
||||
$body = $rewriteImages($cleanBody($content));
|
||||
preg_match_all('/(?<=\s|^)#([a-zA-Z0-9_\/-]+)/m', $body, $tm);
|
||||
$tags = implode(',', array_unique(array_map('strtolower', $tm[1] ?? [])));
|
||||
|
||||
$db->prepare('INSERT INTO cards (title, body, color, tags, position, bucket_id) VALUES (?, ?, ?, ?, -1, ?)')
|
||||
->execute([$title, trim($body), '#8b5cf6', $tags, $importBucket]);
|
||||
$imported++;
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
echo json_encode(['ok' => true, 'imported' => $imported, 'assets' => count($assetMap)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// CalDAV endpoints
|
||||
$caldav = new CalDavSync($db);
|
||||
|
||||
@@ -1251,7 +1125,6 @@ if (str_starts_with($route, '/api/')) {
|
||||
<footer class="shrink-0 z-40 border-t border-t-border bg-t-bg shadow-[0_-4px_20px_-4px_rgba(0,0,0,0.3)] px-6 py-2 flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<input type="file" id="import-file" accept=".zip,application/zip" class="hidden">
|
||||
<input type="file" id="import-logseq-file" accept=".zip,application/zip" class="hidden">
|
||||
<button id="logout-btn" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-danger hover:text-t-danger transition-colors">
|
||||
<i class="ph ph-sign-out text-sm"></i><span class="font-pixel text-[10px]">logout</span>
|
||||
</button>
|
||||
@@ -1261,9 +1134,6 @@ if (str_starts_with($route, '/api/')) {
|
||||
<button id="export-btn" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors">
|
||||
<i class="ph ph-download-simple text-sm"></i><span class="font-pixel text-[10px]">export</span>
|
||||
</button>
|
||||
<button id="import-logseq-btn" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors">
|
||||
<i class="ph ph-graph text-sm"></i><span class="font-pixel text-[10px]">logseq import</span>
|
||||
</button>
|
||||
<button id="export-md-btn" class="flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors">
|
||||
<i class="ph ph-vault text-sm"></i><span class="font-pixel text-[10px]">export vault</span>
|
||||
</button>
|
||||
|
||||
Reference in new issue
Block a user