140 lines
5.2 KiB
Dart
140 lines
5.2 KiB
Dart
|
|
import '../../database/database.dart';
|
||
|
|
import '../../l10n/strings.dart';
|
||
|
|
import '../../mews/document.dart';
|
||
|
|
import 'mews_urls.dart';
|
||
|
|
import 'page_blocks.dart';
|
||
|
|
|
||
|
|
/// Every text the reader kept from a page, with the page it came from. The
|
||
|
|
/// page mirrors the posts page: a searchable list ordered by the day the
|
||
|
|
/// text was kept or by the page it came from, one row per quotation, each
|
||
|
|
/// row carrying the citation's author when the page named one.
|
||
|
|
Future<MewsPage> buildQuotationsPage(
|
||
|
|
Uri url, {
|
||
|
|
required MewsDatabase db,
|
||
|
|
required Strings strings,
|
||
|
|
}) async {
|
||
|
|
final query = url.queryParameters['q'] ?? '';
|
||
|
|
final sort = QuotationSort.parse(url.queryParameters['sort']);
|
||
|
|
final quotations = await db.quotationsList(sort: sort, query: query);
|
||
|
|
final total = await db.countQuotations();
|
||
|
|
final title = strings.t('quotations-title');
|
||
|
|
|
||
|
|
return internalPage(
|
||
|
|
url: url,
|
||
|
|
title: title,
|
||
|
|
strings: strings,
|
||
|
|
body: [
|
||
|
|
...chrome(title: title, current: url, strings: strings),
|
||
|
|
textForm(
|
||
|
|
action: MewsUrls.quotations,
|
||
|
|
label: strings.t('quotations-search-label'),
|
||
|
|
submitLabel: strings.t('action-search'),
|
||
|
|
field: 'q',
|
||
|
|
value: query,
|
||
|
|
),
|
||
|
|
...linkLine([
|
||
|
|
if (query.isNotEmpty)
|
||
|
|
action(MewsUrls.page(url, {'q': null}),
|
||
|
|
strings.t('action-clear-search')),
|
||
|
|
]),
|
||
|
|
...choiceLine(
|
||
|
|
label: strings.t('posts-sort-label'),
|
||
|
|
options: [
|
||
|
|
for (final option in const [
|
||
|
|
(QuotationSort.date, 'posts-sort-date'),
|
||
|
|
(QuotationSort.page, 'quotations-sort-page'),
|
||
|
|
])
|
||
|
|
(
|
||
|
|
href: MewsUrls.page(url, {'sort': option.$1.name}),
|
||
|
|
label: strings.t(option.$2),
|
||
|
|
current: option.$1 == sort,
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
const RuleBlock(),
|
||
|
|
ParagraphBlock([
|
||
|
|
MewsEmphasis(EmphasisStyle.emphasis, [
|
||
|
|
MewsText(countText(strings, total,
|
||
|
|
none: 'count-quotations-none',
|
||
|
|
one: 'count-quotations-one',
|
||
|
|
many: 'count-quotations-many')),
|
||
|
|
]),
|
||
|
|
]),
|
||
|
|
if (quotations.isEmpty)
|
||
|
|
ParagraphBlock([
|
||
|
|
MewsText(strings.t(
|
||
|
|
query.isEmpty ? 'quotations-empty' : 'quotations-no-matches')),
|
||
|
|
])
|
||
|
|
else
|
||
|
|
itemList([
|
||
|
|
for (final quotation in quotations)
|
||
|
|
itemRow(
|
||
|
|
href: quotation.pageUrl,
|
||
|
|
label: quotation.quote,
|
||
|
|
date: dayText(quotation.createdAt),
|
||
|
|
// The row names the page the text came from, not just the
|
||
|
|
// text: the author and the article's title when they are
|
||
|
|
// known, and the site by its own title when the reader's
|
||
|
|
// subscriptions know it, otherwise by its host.
|
||
|
|
note: _rowNote(quotation),
|
||
|
|
actions: [
|
||
|
|
action(
|
||
|
|
MewsUrls.action('share-quotation', {'id': '${quotation.id}'}),
|
||
|
|
strings.t('quotations-share')),
|
||
|
|
action(
|
||
|
|
MewsUrls.action(
|
||
|
|
'remove-quotation', {'id': '${quotation.id}'}),
|
||
|
|
strings.t('quotations-remove')),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
]),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Who and what the row names behind a quotation: the author and the
|
||
|
|
/// article's title when they are known, and the site the page came from by
|
||
|
|
/// its own title, or by its host when the subscriptions do not know it.
|
||
|
|
/// Nothing is named twice.
|
||
|
|
String? _rowNote(Quotation quotation) {
|
||
|
|
final host = Uri.tryParse(quotation.pageUrl)?.host;
|
||
|
|
final source = (quotation.siteTitle?.trim().isNotEmpty ?? false)
|
||
|
|
? quotation.siteTitle!.trim()
|
||
|
|
: (host != null && host.isNotEmpty ? host : quotation.pageUrl);
|
||
|
|
final named = <String>{};
|
||
|
|
final parts = [
|
||
|
|
for (final part in [
|
||
|
|
quotation.author?.trim(),
|
||
|
|
quotation.pageTitle?.trim(),
|
||
|
|
source,
|
||
|
|
])
|
||
|
|
if (part != null && part.isNotEmpty && named.add(part)) part,
|
||
|
|
];
|
||
|
|
return parts.isEmpty ? null : parts.join(actionSeparator);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The citation a quotation travels with, shaped as APA names a web page:
|
||
|
|
/// the author first when the page's metadata carried one, otherwise the
|
||
|
|
/// article's title; the year as a no-date marker, since a page declares no
|
||
|
|
/// publication date; then the article's title, the site's own title and the
|
||
|
|
/// address. The site is named by its title when the reader's subscriptions
|
||
|
|
/// know it, and by its host when they do not; nothing is named twice.
|
||
|
|
String citationFor(Quotation quotation, Strings strings) {
|
||
|
|
final noDate = strings.t('quotations-no-date');
|
||
|
|
final uri = Uri.tryParse(quotation.pageUrl);
|
||
|
|
final host =
|
||
|
|
uri != null && uri.host.isNotEmpty ? uri.host : quotation.pageUrl;
|
||
|
|
final author = quotation.author?.trim();
|
||
|
|
final title = quotation.pageTitle?.trim();
|
||
|
|
final site = (quotation.siteTitle?.trim().isNotEmpty ?? false)
|
||
|
|
? quotation.siteTitle!.trim()
|
||
|
|
: host;
|
||
|
|
final lead = (author != null && author.isNotEmpty) ? author : title ?? site;
|
||
|
|
final named = [
|
||
|
|
if (lead != title && title != null && title.isNotEmpty) title,
|
||
|
|
if (site != lead && site != title) site,
|
||
|
|
];
|
||
|
|
if (named.isEmpty) return '$lead ($noDate). ${quotation.pageUrl}.';
|
||
|
|
return '$lead ($noDate). ${named.join('. ')}. ${quotation.pageUrl}.';
|
||
|
|
}
|