Release 0.6.1
This commit is contained in:
1 parent
b80dded55d
commit
d586fbd8ce
29 files changed
+398
-66
No files matched your search
+34
-12
@@ -14,6 +14,7 @@ import {
|
||||
export const PIECE_STATUSES = ['inbox', 'preparing', 'ready', 'archived'] as const;
|
||||
export type PieceStatus = (typeof PIECE_STATUSES)[number];
|
||||
export type EditablePieceStatus = Exclude<PieceStatus, 'archived'>;
|
||||
export type PreparationEffort = 'low' | 'medium' | 'high' | null;
|
||||
export type TaxonomyKind = 'category' | 'format' | 'topic';
|
||||
|
||||
export type PieceSourceInput = {
|
||||
@@ -42,6 +43,7 @@ export type PieceInput = {
|
||||
summary: string;
|
||||
formatId: string | null;
|
||||
status: EditablePieceStatus;
|
||||
preparationEffort: PreparationEffort;
|
||||
durationSeconds: number;
|
||||
intro: string;
|
||||
researchText: string;
|
||||
@@ -82,6 +84,7 @@ export type PieceListItem = {
|
||||
sourceCount: number;
|
||||
mediaId: string | null;
|
||||
isFavorite: boolean;
|
||||
preparationEffort: PreparationEffort;
|
||||
};
|
||||
|
||||
export type PieceEditor = Omit<
|
||||
@@ -198,6 +201,9 @@ function validatePieceInput(input: PieceInput): ValidatedPieceInput {
|
||||
const title = cleanText(input.title, 200, 'title');
|
||||
if (!title) throw new Error('INVALID_TITLE');
|
||||
if (!['inbox', 'preparing', 'ready'].includes(input.status)) throw new Error('INVALID_STATUS');
|
||||
if (![null, 'low', 'medium', 'high'].includes(input.preparationEffort)) {
|
||||
throw new Error('INVALID_PREPARATION_EFFORT');
|
||||
}
|
||||
if (
|
||||
!Number.isInteger(input.durationSeconds) ||
|
||||
input.durationSeconds < 0 ||
|
||||
@@ -226,7 +232,7 @@ function validatePieceInput(input: PieceInput): ValidatedPieceInput {
|
||||
}
|
||||
return { label, url, timecodeSeconds: source.timecodeSeconds };
|
||||
});
|
||||
if (!['text', 'link', 'video'].includes(input.presentationMode)) {
|
||||
if (!['text', 'image', 'link', 'video'].includes(input.presentationMode)) {
|
||||
throw new Error('INVALID_PRESENTATION');
|
||||
}
|
||||
const sourcePosition = input.presentationSourcePosition;
|
||||
@@ -236,7 +242,7 @@ function validatePieceInput(input: PieceInput): ValidatedPieceInput {
|
||||
) {
|
||||
throw new Error('INVALID_PRESENTATION');
|
||||
}
|
||||
if (input.presentationMode !== 'text' && sourcePosition === null) {
|
||||
if (['link', 'video'].includes(input.presentationMode) && sourcePosition === null) {
|
||||
throw new Error('INVALID_PRESENTATION');
|
||||
}
|
||||
const presentationImageUrl = cleanText(input.presentationImageUrl, 2_000, 'presentation_image');
|
||||
@@ -249,12 +255,16 @@ function validatePieceInput(input: PieceInput): ValidatedPieceInput {
|
||||
input.presentationMediaId === undefined
|
||||
? undefined
|
||||
: cleanText(input.presentationMediaId, 100, 'presentation_media') || null;
|
||||
if (input.presentationMode === 'image' && !presentationMediaId) {
|
||||
throw new Error('INVALID_PRESENTATION');
|
||||
}
|
||||
return {
|
||||
title,
|
||||
outputTitle: cleanText(input.outputTitle, 300, 'output_title'),
|
||||
summary: cleanText(input.summary, 2_000, 'summary'),
|
||||
formatId: input.formatId || null,
|
||||
status: input.status,
|
||||
preparationEffort: input.preparationEffort,
|
||||
durationSeconds: input.durationSeconds,
|
||||
intro: cleanText(input.intro, 50_000, 'intro'),
|
||||
researchText: cleanText(input.researchText, 50_000, 'research'),
|
||||
@@ -317,6 +327,7 @@ type PieceListRow = {
|
||||
source_count: number;
|
||||
media_id: string | null;
|
||||
is_favorite: number;
|
||||
preparation_effort: PreparationEffort;
|
||||
};
|
||||
|
||||
export function listPieces(filters: {
|
||||
@@ -366,6 +377,7 @@ export function listPieces(filters: {
|
||||
const rows = database
|
||||
.prepare(
|
||||
`SELECT p.id, p.title, p.summary, p.status, p.revision, p.duration_seconds, p.is_favorite,
|
||||
p.preparation_effort,
|
||||
p.format_id, COALESCE(f.name, 'Ohne Format') AS format,
|
||||
(SELECT count(*) FROM piece_sources ps WHERE ps.piece_id = p.id) AS source_count,
|
||||
(SELECT pm.id FROM piece_media pm WHERE pm.piece_id = p.id AND pm.removed_at_utc IS NULL ORDER BY pm.position LIMIT 1) AS media_id
|
||||
@@ -398,7 +410,8 @@ export function listPieces(filters: {
|
||||
topics: topicStatement.all(row.id) as PieceListItem['topics'],
|
||||
sourceCount: row.source_count,
|
||||
mediaId: row.media_id,
|
||||
isFavorite: row.is_favorite === 1
|
||||
isFavorite: row.is_favorite === 1,
|
||||
preparationEffort: row.preparation_effort
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -433,14 +446,16 @@ type PieceRow = {
|
||||
updated_at_utc: string;
|
||||
is_favorite: number;
|
||||
presentation_media_id: string | null;
|
||||
preparation_effort: PreparationEffort;
|
||||
};
|
||||
|
||||
export function getPiece(id: string): PieceEditor | null {
|
||||
const database = getDatabase();
|
||||
const row = database
|
||||
.prepare(
|
||||
`SELECT id, title, output_title, summary, format_id, status, duration_seconds, intro, research_text,
|
||||
moderation_notes, interaction_text, transition_text, presentation_text, presentation_mode,
|
||||
`SELECT id, title, output_title, summary, format_id, status, preparation_effort, duration_seconds, intro, research_text,
|
||||
moderation_notes, interaction_text, transition_text, presentation_text,
|
||||
CASE WHEN presentation_image_only = 1 THEN 'image' ELSE presentation_mode END AS presentation_mode,
|
||||
presentation_source_position, presentation_title, presentation_description,
|
||||
presentation_image_url, presentation_site_name, presentation_provider,
|
||||
presentation_provider_id, presentation_title_manual, presentation_description_manual,
|
||||
@@ -460,6 +475,7 @@ export function getPiece(id: string): PieceEditor | null {
|
||||
summary: row.summary,
|
||||
formatId: row.format_id,
|
||||
status: row.status,
|
||||
preparationEffort: row.preparation_effort,
|
||||
durationSeconds: row.duration_seconds,
|
||||
intro: row.intro,
|
||||
researchText: row.research_text,
|
||||
@@ -612,9 +628,9 @@ export function savePiece(id: string, expectedRevision: number, rawInput: PieceI
|
||||
const save = database.transaction(() => {
|
||||
const result = database
|
||||
.prepare(
|
||||
`UPDATE pieces SET title = ?, output_title = ?, summary = ?, format_id = ?, status = ?, duration_seconds = ?,
|
||||
`UPDATE pieces SET title = ?, output_title = ?, summary = ?, format_id = ?, status = ?, preparation_effort = ?, duration_seconds = ?,
|
||||
intro = ?, research_text = ?, moderation_notes = ?, interaction_text = COALESCE(?, interaction_text), transition_text = ?, presentation_text = ?,
|
||||
presentation_mode = ?, presentation_source_position = ?, presentation_title = ?,
|
||||
presentation_mode = ?, presentation_image_only = ?, presentation_source_position = ?, presentation_title = ?,
|
||||
presentation_description = ?, presentation_image_url = ?, presentation_site_name = ?,
|
||||
presentation_provider = ?, presentation_provider_id = ?, presentation_title_manual = ?,
|
||||
presentation_description_manual = ?, presentation_image_manual = ?,
|
||||
@@ -628,6 +644,7 @@ export function savePiece(id: string, expectedRevision: number, rawInput: PieceI
|
||||
input.summary,
|
||||
input.formatId,
|
||||
input.status,
|
||||
input.preparationEffort,
|
||||
input.durationSeconds,
|
||||
input.intro,
|
||||
input.researchText,
|
||||
@@ -635,7 +652,8 @@ export function savePiece(id: string, expectedRevision: number, rawInput: PieceI
|
||||
input.interactionText ?? null,
|
||||
input.transitionText,
|
||||
input.presentationText,
|
||||
input.presentationMode,
|
||||
input.presentationMode === 'image' ? 'text' : input.presentationMode,
|
||||
input.presentationMode === 'image' ? 1 : 0,
|
||||
input.presentationSourcePosition,
|
||||
input.presentationTitle,
|
||||
input.presentationDescription,
|
||||
@@ -671,6 +689,7 @@ function blankPieceInput(): ValidatedPieceInput {
|
||||
summary: '',
|
||||
formatId: null,
|
||||
status: 'preparing',
|
||||
preparationEffort: null,
|
||||
durationSeconds: 0,
|
||||
intro: '',
|
||||
researchText: '',
|
||||
@@ -798,6 +817,7 @@ export function savePieceAsTemplate(
|
||||
delete snapshot.updatedAtUtc;
|
||||
delete snapshot.media;
|
||||
delete snapshot.presentationMediaId;
|
||||
if (snapshot.presentationMode === 'image') snapshot.presentationMode = 'text';
|
||||
snapshot.status = 'preparing';
|
||||
const snapshotJson = JSON.stringify(snapshot);
|
||||
const database = getDatabase();
|
||||
@@ -917,15 +937,15 @@ export async function duplicatePiece(sourceId: string): Promise<string> {
|
||||
database.transaction(() => {
|
||||
database
|
||||
.prepare(
|
||||
`INSERT INTO pieces (id, title, output_title, summary, format_id, status, duration_seconds, intro,
|
||||
research_text, moderation_notes, interaction_text, transition_text, presentation_text, presentation_mode,
|
||||
`INSERT INTO pieces (id, title, output_title, summary, format_id, status, preparation_effort, duration_seconds, intro,
|
||||
research_text, moderation_notes, interaction_text, transition_text, presentation_text, presentation_mode, presentation_image_only,
|
||||
presentation_source_position, presentation_title, presentation_description,
|
||||
presentation_image_url, presentation_site_name, presentation_provider,
|
||||
presentation_provider_id, presentation_title_manual,
|
||||
presentation_description_manual, presentation_image_manual,
|
||||
presentation_metadata_fetched_at_utc, created_at_utc, updated_at_utc, revision)
|
||||
VALUES (
|
||||
?, ?, ?, ?, ?, 'preparing', ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, 'preparing', ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1
|
||||
)`
|
||||
)
|
||||
@@ -935,6 +955,7 @@ export async function duplicatePiece(sourceId: string): Promise<string> {
|
||||
source.outputTitle,
|
||||
source.summary,
|
||||
source.formatId,
|
||||
source.preparationEffort,
|
||||
source.durationSeconds,
|
||||
source.intro,
|
||||
source.researchText,
|
||||
@@ -942,7 +963,8 @@ export async function duplicatePiece(sourceId: string): Promise<string> {
|
||||
source.interactionText,
|
||||
source.transitionText,
|
||||
source.presentationText,
|
||||
source.presentationMode,
|
||||
source.presentationMode === 'image' ? 'text' : source.presentationMode,
|
||||
source.presentationMode === 'image' ? 1 : 0,
|
||||
source.presentationSourcePosition,
|
||||
source.presentationTitle,
|
||||
source.presentationDescription,
|
||||
|
||||
Reference in new issue
Block a user