feat(publish): a release keeps its colour, and a description for Explore

Publishing takes a description for Explore (rich text, as its JSON),
which Explore shows in place of the studyset's own: a studyset may
introduce itself to strangers otherwise than in the library, where its
author may leave it empty. Without one, as from older apps, the
studyset's description as before; one that isn't rich text, or over
100,000 characters, counts as none. A release also keeps the studyset's
colour, which Explore gives out with it; releases before have none. The
publication's releases say their description, for the next publish to
start from.

Migration release-colour adds a nullable column:
ALTER TABLE "studyset_release" ADD COLUMN "colorName" text;
the server before it reads and writes the table as ever.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-14 17:40:33 +02:00
commit fe6b1f5ed1
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
3 changed files with 14958 additions and 15 deletions

View file

@ -746,6 +746,11 @@ class EndpointPublish extends _isc.EndpointRef {
/// Publishes your studyset [studysetId] with everything inside it as its
/// next release, [notes] saying what changed; at the first, under the
/// name [slug] in its location, which stays its name from then on.
/// [description] (rich text, as its JSON) is what Explore shows of it,
/// when given: a studyset may introduce itself to strangers otherwise
/// than in the library (decided 2026-10-14); without, the studyset's
/// own description. One that isn't rich text, or is larger than
/// [maxExploreDescription], counts as not given.
///
/// Throws [PublishRefusedException] when it can't be published (see
/// [PublishRefusal]), and [NotFoundException] for a studyset that isn't
@ -753,14 +758,16 @@ class EndpointPublish extends _isc.EndpointRef {
_ida.Future<_ij0cqo11.Publication> publish(
_isc.UuidValue studysetId,
String slug,
String notes,
) => caller.callServerEndpoint<_ij0cqo11.Publication>(
String notes, {
String? description,
}) => caller.callServerEndpoint<_ij0cqo11.Publication>(
'publish',
'publish',
{
'studysetId': studysetId,
'slug': slug,
'notes': notes,
'description': description,
},
);

View file

@ -31,6 +31,7 @@ abstract class ExploreEntry
this.frontLanguage,
this.backLanguage,
this.basedOn,
this.colorName,
required this.license,
this.licenseHolder,
this.licenseYear,
@ -57,6 +58,7 @@ abstract class ExploreEntry
String? frontLanguage,
String? backLanguage,
String? basedOn,
String? colorName,
required String license,
String? licenseHolder,
int? licenseYear,
@ -88,6 +90,7 @@ abstract class ExploreEntry
frontLanguage: jsonSerialization['frontLanguage'] as String?,
backLanguage: jsonSerialization['backLanguage'] as String?,
basedOn: jsonSerialization['basedOn'] as String?,
colorName: jsonSerialization['colorName'] as String?,
license: jsonSerialization['license'] as String,
licenseHolder: jsonSerialization['licenseHolder'] as String?,
licenseYear: jsonSerialization['licenseYear'] as int?,
@ -135,6 +138,9 @@ abstract class ExploreEntry
/// Where the published studyset it is a copy of is, for a copy.
String? basedOn;
/// Its colour, by the app palette's name for it; null for none.
String? colorName;
/// Its licence and credits.
String license;
@ -175,6 +181,7 @@ abstract class ExploreEntry
String? frontLanguage,
String? backLanguage,
String? basedOn,
String? colorName,
String? license,
String? licenseHolder,
int? licenseYear,
@ -203,6 +210,7 @@ abstract class ExploreEntry
if (frontLanguage != null) 'frontLanguage': frontLanguage,
if (backLanguage != null) 'backLanguage': backLanguage,
if (basedOn != null) 'basedOn': basedOn,
if (colorName != null) 'colorName': colorName,
'license': license,
if (licenseHolder != null) 'licenseHolder': licenseHolder,
if (licenseYear != null) 'licenseYear': licenseYear,
@ -233,6 +241,7 @@ abstract class ExploreEntry
if (frontLanguage != null) 'frontLanguage': frontLanguage,
if (backLanguage != null) 'backLanguage': backLanguage,
if (basedOn != null) 'basedOn': basedOn,
if (colorName != null) 'colorName': colorName,
'license': license,
if (licenseHolder != null) 'licenseHolder': licenseHolder,
if (licenseYear != null) 'licenseYear': licenseYear,
@ -269,6 +278,7 @@ class _ExploreEntryImpl extends ExploreEntry {
String? frontLanguage,
String? backLanguage,
String? basedOn,
String? colorName,
required String license,
String? licenseHolder,
int? licenseYear,
@ -293,6 +303,7 @@ class _ExploreEntryImpl extends ExploreEntry {
frontLanguage: frontLanguage,
backLanguage: backLanguage,
basedOn: basedOn,
colorName: colorName,
license: license,
licenseHolder: licenseHolder,
licenseYear: licenseYear,
@ -323,6 +334,7 @@ class _ExploreEntryImpl extends ExploreEntry {
Object? frontLanguage = _Undefined,
Object? backLanguage = _Undefined,
Object? basedOn = _Undefined,
Object? colorName = _Undefined,
String? license,
Object? licenseHolder = _Undefined,
Object? licenseYear = _Undefined,
@ -352,6 +364,7 @@ class _ExploreEntryImpl extends ExploreEntry {
: this.frontLanguage,
backLanguage: backLanguage is String? ? backLanguage : this.backLanguage,
basedOn: basedOn is String? ? basedOn : this.basedOn,
colorName: colorName is String? ? colorName : this.colorName,
license: license ?? this.license,
licenseHolder: licenseHolder is String?
? licenseHolder

View file

@ -25,6 +25,7 @@ abstract class ReleaseInfo
required this.pathCount,
required this.pdfCount,
required this.releasedAt,
this.description,
});
factory ReleaseInfo({
@ -37,6 +38,7 @@ abstract class ReleaseInfo
required int pathCount,
required int pdfCount,
required DateTime releasedAt,
String? description,
}) = _ReleaseInfoImpl;
factory ReleaseInfo.fromJson(Map<String, dynamic> jsonSerialization) {
@ -52,6 +54,7 @@ abstract class ReleaseInfo
releasedAt: _isc.DateTimeJsonExtension.fromJson(
jsonSerialization['releasedAt'],
),
description: jsonSerialization['description'] as String?,
);
}
@ -77,6 +80,10 @@ abstract class ReleaseInfo
/// When it was released, in UTC.
DateTime releasedAt;
/// What Explore shows of it as its description (rich text, as its JSON);
/// null for none.
String? description;
/// Returns a shallow copy of this [ReleaseInfo]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@ -90,6 +97,7 @@ abstract class ReleaseInfo
int? pathCount,
int? pdfCount,
DateTime? releasedAt,
String? description,
});
@override
Map<String, dynamic> toJson() {
@ -104,6 +112,7 @@ abstract class ReleaseInfo
'pathCount': pathCount,
'pdfCount': pdfCount,
'releasedAt': releasedAt.toJson(),
if (description != null) 'description': description,
};
}
@ -120,6 +129,7 @@ abstract class ReleaseInfo
'pathCount': pathCount,
'pdfCount': pdfCount,
'releasedAt': releasedAt.toJson(),
if (description != null) 'description': description,
};
}
@ -129,6 +139,8 @@ abstract class ReleaseInfo
}
}
class _Undefined {}
class _ReleaseInfoImpl extends ReleaseInfo {
_ReleaseInfoImpl({
required int version,
@ -140,6 +152,7 @@ class _ReleaseInfoImpl extends ReleaseInfo {
required int pathCount,
required int pdfCount,
required DateTime releasedAt,
String? description,
}) : super._(
version: version,
notes: notes,
@ -150,6 +163,7 @@ class _ReleaseInfoImpl extends ReleaseInfo {
pathCount: pathCount,
pdfCount: pdfCount,
releasedAt: releasedAt,
description: description,
);
/// Returns a shallow copy of this [ReleaseInfo]
@ -166,6 +180,7 @@ class _ReleaseInfoImpl extends ReleaseInfo {
int? pathCount,
int? pdfCount,
DateTime? releasedAt,
Object? description = _Undefined,
}) {
return ReleaseInfo(
version: version ?? this.version,
@ -177,6 +192,7 @@ class _ReleaseInfoImpl extends ReleaseInfo {
pathCount: pathCount ?? this.pathCount,
pdfCount: pdfCount ?? this.pdfCount,
releasedAt: releasedAt ?? this.releasedAt,
description: description is String? ? description : this.description,
);
}
}