~sql в пузыре кодоблока, кликабельные имена и пути

- renderSqlBlock: та же рамка что у кода (Telegram-пузырь), таблица
  светлым по тёмно-синему, кнопка копирования запроса
- Имя файла (title) — ссылка flora://open (новая вкладка рядом);
  путь — ссылка flora://copycode/spath_N (клик копирует путь)
- Database::filePathForTitle: резолв имени в путь (пусто при
  отсутствии/неоднозначности)
- Тесты пузыря, open/copy ссылок и резолва
This commit is contained in:
2026-09-07 17:03:07 +03:00
parent 8e8cf3ce70
commit 51372ace4e
4 changed files with 196 additions and 75 deletions
+49 -5
View File
@@ -4,6 +4,7 @@
#include <QTemporaryDir>
#include <QDir>
#include <QFile>
#include <QUrl>
#include <cstdio>
#include "core/Frontmatter.h"
@@ -193,9 +194,11 @@ int main(int argc, char *argv[])
if (blocks[1].toMap().value("type").toString() != "html")
return fail("sql type", blocks[1].toMap().value("type").toString());
const QString html = blocks[1].toMap().value("html").toString();
// title = имя файла без расширения
if (!html.contains("<table") || !html.contains(">a</td>") || !html.contains(">b</td>"))
// title = имя файла без расширения; пузырь кодоблока; имена — ссылки
if (!html.contains("<table") || !html.contains(">a</a>") || !html.contains(">b</a>"))
return fail("sql table", html.left(300));
if (!html.contains("#2e4d6a"))
return fail("sql bubble", html.left(200));
// plain render тоже таблицей
const QString h = p.render("```~sql\nSELECT title FROM files\n```\n", vault, vault);
if (!h.contains("<table"))
@@ -316,16 +319,57 @@ int main(int argc, char *argv[])
if (r.value("rows").toList().value(0).toList().value(0).toString() != "0")
return fail("isolate c");
}
// ~sql внутри index.md видит другие файлы (и не видит изолят)
// filePathForTitle: точное имя → путь, нет/несколько → пусто
{
if (db2.filePathForTitle("a") != v2 + "/a.md")
return fail("title lookup", db2.filePathForTitle("a"));
if (!db2.filePathForTitle("нет-такого").isEmpty())
return fail("title missing");
}
// ~sql внутри index.md видит другие файлы (и не видит изолят).
// Имена — ссылки flora://open, пути — flora://copycode (клик = в буфер).
{
MdParser p;
p.setDatabaseForVault(v2, &db2);
const QVariantList blocks = p.parseBlocks(
"```~sql\nSELECT title, path FROM files WHERE title IN ('a','b','c') ORDER BY title\n```\n",
v2, v2);
if (blocks.size() != 1 || blocks[0].toMap().value("type").toString() != "html")
return fail("xfile type");
const QString h = blocks[0].toMap().value("html").toString();
if (!h.contains("#2e4d6a"))
return fail("xfile bubble", h.left(200));
// имя a открывает файл
const QString openA = "flora://open/" + QString::fromLatin1(QUrl::toPercentEncoding(v2 + "/a.md"));
if (!h.contains(openA))
return fail("xfile open link", h.left(500));
// путь a копируется (spath_* зарегистрирован)
if (!h.contains("flora://copycode/spath_"))
return fail("xfile copy link", h.left(500));
if (p.getCodeBlock("spath_0") != v2 + "/a.md"
&& p.getCodeBlock("spath_1") != v2 + "/a.md"
&& p.getCodeBlock("spath_2") != v2 + "/a.md")
return fail("xfile copy target");
// изолят c тоже в выдаче (запрос его выбрал), но имя так же кликабельно
if (!h.contains(">c</a>"))
return fail("xfile c link", h.left(500));
// title без path в строке резолвится через базу
const QVariantList b2 = p.parseBlocks("```~sql\nSELECT title FROM files WHERE title='b'\n```\n", v2, v2);
const QString h2 = b2[0].toMap().value("html").toString();
const QString openB = "flora://open/" + QString::fromLatin1(QUrl::toPercentEncoding(v2 + "/b.md"));
if (!h2.contains(openB))
return fail("xfile title lookup", h2.left(500));
}
// Рендер index.md целиком: таблица есть, изолята нет, шапки нет
{
MdParser p;
p.setDatabaseForVault(v2, &db2);
QFile f(v2 + "/index.md");
f.open(QIODevice::ReadOnly);
const QString h = p.render(QString::fromUtf8(f.readAll()), v2, v2);
if (!h.contains(">a</td>") || !h.contains(">b</td>"))
if (!h.contains(">a</a>") || !h.contains(">b</a>"))
return fail("xfile table", h.left(300));
if (h.contains(">c</td>"))
if (h.contains(">c</td>") || h.contains(">c</a>"))
return fail("xfile leak isolate");
if (h.contains("тип: хаб"))
return fail("xfile fm leak");