feat: add Gemini and Spartan protocol support

This commit is contained in:
randogoth 2026-10-11 22:07:25 +03:00
parent a4a49f41d7
commit fa7aa5557c
48 changed files with 4992 additions and 161 deletions

View file

@ -0,0 +1,82 @@
import '../../database/database.dart';
import '../../l10n/strings.dart';
import '../../mews/document.dart';
import 'mews_urls.dart';
import 'page_blocks.dart';
/// The reader's certificates, worn as identities on Gemini sites that ask for
/// one. An identity is added from a PEM file, renamed in place, and removed
/// in a second step when it is worn anywhere, because the sites that know it
/// lose it with the removal.
Future<MewsPage> buildCertificatesPage(
Uri url, {
required MewsDatabase db,
required Strings strings,
}) async {
final title = strings.t('certificates-title');
final held = await db.allClientCertificates();
final body = <MewsBlock>[
...chrome(title: title, current: url, strings: strings),
ParagraphBlock([
MewsLink(MewsUrls.action('import-certificate'), [
MewsText(strings.t('certificates-add')),
]),
]),
];
// Removing an identity takes a second step, like clearing history: the
// sites it is worn at stop seeing it too, and that cannot be undone.
final forgetting = int.tryParse(url.queryParameters['forget'] ?? '');
final row = forgetting == null ? null : await db.clientCertificate(forgetting);
if (row != null) {
body.addAll([
HeadingBlock(2, [MewsText(row.name)]),
ParagraphBlock([MewsText(strings.t('certificates-delete-confirm'))]),
...linkLine([
action(
MewsUrls.action('delete-certificate', {'id': '${row.id}'}),
strings.t('certificates-delete-yes')),
action(MewsUrls.certificates, strings.t('certificates-delete-no')),
]),
]);
}
if (held.isEmpty) {
body.add(ParagraphBlock([MewsText(strings.t('certificates-empty'))]));
} else {
final rows = <MewsListItem>[];
for (final certificate in held) {
final worn = await db.bindingsOf(certificate.id);
rows.add(MewsListItem([
ParagraphBlock([
MewsEmphasis(EmphasisStyle.strong, [MewsText(certificate.name)]),
if (worn.isNotEmpty) ...[
const MewsLineBreak(),
MewsEmphasis(
EmphasisStyle.emphasis,
[
MewsText(worn
.map((host) => strings.t('certificates-bound-to', {'host': host}))
.join(actionSeparator)),
],
),
],
const MewsLineBreak(),
MewsLink(MewsUrls.page(url, {'forget': '${certificate.id}'}),
[MewsText(strings.t('certificates-remove'))]),
]),
textForm(
action: MewsUrls.action('rename-certificate', {'id': '${certificate.id}'}),
field: 'name',
label: strings.t('certificates-rename-label'),
submitLabel: strings.t('certificates-rename-submit'),
value: certificate.name,
),
]));
}
body.add(itemList(rows));
}
return internalPage(url: url, title: title, body: body, strings: strings);
}