Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/backend/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export async function configure(command: Configure) {

await codemods.makeUsingStub(stubsRoot, 'resources/layout.stub', {});
await codemods.makeUsingStub(stubsRoot, 'resources/views/preview.stub', {});
await codemods.makeUsingStub(stubsRoot, 'resources/views/preview_devotion.stub', {});
await codemods.makeUsingStub(stubsRoot, 'resources/views/scripture.stub', {});

await codemods.makeUsingStub(stubsRoot, 'commands/migrate.stub', {});
Expand Down
11 changes: 11 additions & 0 deletions src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ export * from './validators/invitation.js';
export * from './validators/bundle.js';
export * from './validators/chapter.js';
export * from './validators/course.js';
export * from './validators/devotion_draft.js';
export {
createDevotionDraftBundle,
normalizeDevotionDraftBundle,
} from '../shared/devotion_draft.js';
export { resolvePreviewBundle } from '../shared/preview_bundle.js';
export { cloneBlocksStructure } from '../shared/block_structure.js';
export {
getPreviousDevotionChapterBlocks,
type StoryChapterSpecifier,
} from '../shared/previous_chapter_blocks.js';
export { default as DropValidator } from './validators/drop.js';
export { default as PageValidator } from './validators/page.js';
export * from './validators/user.js';
Expand Down
6 changes: 6 additions & 0 deletions src/backend/services/draft_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import Chapter from '../models/chapter.js';
import type { FieldMap, FieldSpec, StorySpec, StoryVersion, JSON } from '../../types';
import { BundleService } from './bundle_service.js';
import { CmsService } from './cms_service.js';
import { createDevotionDraftBundle } from '../../shared/devotion_draft.js';
import { isDevotionTemplate } from '../../shared/story_helpers.js';

export class DraftService {
public story: StorySpec;
Expand All @@ -25,6 +27,10 @@ export class DraftService {
): Promise<JSON<any> | null> {
// is this the source language?
if (version.locale === this.cms.sourceLocale) {
if (isDevotionTemplate(this.story.template)) {
return JSON.stringify(createDevotionDraftBundle(number));
}

const bundleService = new BundleService(this.story.fields);
return bundleService.defaultBundle;
}
Expand Down
2 changes: 2 additions & 0 deletions src/backend/services/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export {
canPublishStory,
canPublishStoryMetadata,
canPublishStoryReady,
DEVOTION_TEMPLATE_ID,
isDevotionTemplate,
missingPublishedChapters,
storyDetailsBlockedMessages,
publishBlockedMessage,
Expand Down
1 change: 1 addition & 0 deletions src/backend/services/story_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ export class StoryService {
return {
id: story.id,
name: localisation?.title ?? '',
template: story.template,
coverImage: localisation?.coverImage ?? '',
chapterLimit: story.chapterLimit,
chapterType: story.chapterType ?? '',
Expand Down
18 changes: 14 additions & 4 deletions src/backend/stubs/controllers/chapters_controller.stub
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import {
type AddStatus,
IndexService,
Chapter,
Draft,
Story,
StoryService,
resolvePreviewBundle,
} from '@story-cms/kit';
import cms from '#services/cms';

Expand Down Expand Up @@ -47,17 +50,24 @@ export default class ChaptersController {
};

const chapter = await Chapter.query().where(specifier).firstOrFail();
const bundleView = await edge.render({{ '`preview_${story.id}`' }}, {
bundle: chapter.bundle,
const draft = await Draft.query().where(specifier).first();
const storyRecord = await Story.findOrFail(story.id);
const bundle = resolvePreviewBundle({
chapter,
draft,
template: storyRecord.template,
});
const bundleView = await edge.render(`preview_${storyRecord.template}`, {
bundle,
marked: marked,
number: chapter.number,
});

const props: PreviewProps = {
chapter: chapter.meta,
bundleView: bundleView,
title: chapter.index.title,
bundle: chapter.bundle,
title: (typeof bundle.title === 'string' ? bundle.title : '') || chapter.index.title,
bundle,
story,
};

Expand Down
52 changes: 51 additions & 1 deletion src/backend/stubs/controllers/drafts_controller.stub
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {
Draft,
DraftService,
IndexService,
ResourceService,
StoryService,
isDevotionTemplate,
normalizeDevotionDraftBundle,
getPreviousDevotionChapterBlocks,
type DevotionDraftEditProps,
type DraftEditProps,
} from '@story-cms/kit';
import cms from '#services/cms';
Expand All @@ -33,12 +38,19 @@ export default class DraftsController {
const bundle = await service.getDraftBundle(version, number);
if (bundle === null) return ctx.response.redirect('/');

await Draft.create({
const draft = await Draft.create({
...version,
number,
bundle,
});

if (
version.locale === cms.sourceLocale &&
isDevotionTemplate(story.template)
) {
ctx.session.flash('newDevotionDraftId', draft.id);
}

await Activity.create({
userId: ctx.auth.user?.id,
locale: version.locale,
Expand Down Expand Up @@ -100,6 +112,44 @@ export default class DraftsController {
const isTranslation = version.locale !== cms.sourceLocale;

if (!isTranslation) {
if (isDevotionTemplate(story.template)) {
const bundle = normalizeDevotionDraftBundle(draft.bundle, draft.number);
const resourceService = new ResourceService();
const [availableResources, resources] = await Promise.all([
resourceService.listForLocale(version.locale),
resourceService.hydrate(bundle.resources),
]);
const createdDraftId = ctx.session.flashMessages.get('newDevotionDraftId');
const previousChapterBlocks =
draft.number > 1
? await getPreviousDevotionChapterBlocks(
{ ...specifier, number: draft.number },
async (spec) => {
const previousDraft = await Draft.query().where(spec).first();
if (previousDraft) {
return previousDraft.bundle;
}

const previousChapter = await Chapter.query().where(spec).first();
return previousChapter?.bundle ?? null;
},
)
: [];
const devotionData: DevotionDraftEditProps = {
...data,
bundle: {
...bundle,
resources,
},
availableResources,
isCreate: Number(createdDraftId) === draft.id,
previousChapterBlocks,
};

// @ts-expect-error Inertia page name
return ctx.inertia.render('DevotionDraftEdit', devotionData);
}

// @ts-expect-error Inertia page name
return ctx.inertia.render('DraftIndex', data);
}
Expand Down
92 changes: 92 additions & 0 deletions src/backend/stubs/resources/views/preview_devotion.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{{{
exports({ to: app.makePath('resources/views/preview_devotion.edge') })
}}}
<section>
@if(bundle.number)
<div class="mb-1 text-sm font-medium leading-5 text-gray-400">Number</div>
<div class="text-lg">\{\{ bundle.number \}\}</div>
@end

@if(bundle.title)
<div class="mt-6 mb-1 text-sm font-medium leading-5 text-gray-400">Title</div>
<div class="text-2xl">\{\{ bundle.title \}\}</div>
@end

@if(bundle.description)
<div class="mt-6 mb-1 text-sm font-medium leading-5 text-gray-400">Description</div>
<div class="max-w-none prose prose-sm">\{\{\{ marked.parse(bundle.description) \}\}\}</div>
@end

@if(bundle.coverImage)
<div class="mt-6 mb-1 text-sm font-medium leading-5 text-gray-400">Cover Image</div>
<img src="\{\{ bundle.coverImage \}\}" alt="\{\{ bundle.title \}\}" class="w-full max-w-xl h-auto rounded" />
@end

@if(bundle.devotionAudio && bundle.devotionAudio.url)
<div class="mt-6 mb-1 text-sm font-medium leading-5 text-gray-400">Devotion Audio</div>
<audio controls>
<source src="\{\{ bundle.devotionAudio.url \}\}" type="audio/mpeg" />
</audio>
@end

@if(bundle.blocks)
@each(block in bundle.blocks)
@if(!block.visibility || !block.visibility.hidden)
<div class="pt-8 mt-8 border-t border-gray-200">
@if(block.blockName)
<div class="mb-1 text-xs font-medium tracking-wide text-gray-400 uppercase">\{\{ block.blockName \}\}</div>
@end

@if(block.kind === 'title')
@if(block.title)
<div class="text-2xl font-semibold">\{\{ block.title \}\}</div>
@end
@if(block.subtitle)
<div class="mt-2 text-lg text-gray-600">\{\{ block.subtitle \}\}</div>
@end
@if(block.coverImage)
<img src="\{\{ block.coverImage \}\}" alt="\{\{ block.title \}\}" class="mt-4 w-full max-w-xl h-auto rounded">
@end
@end

@if(block.kind === 'scripture')
@if(block.displayName)
<div class="mb-2 text-xl font-semibold">\{\{ block.displayName \}\}</div>
@end
@if(block.scripture)
@!component('components/scripture', {passage: block.scripture})
@end
@end

@if(!block.kind || block.kind === 'content')
@if(block.displayName)
<div class="mb-2 text-xl font-semibold">\{\{ block.displayName \}\}</div>
@end
@if(block.content)
<div class="max-w-none prose prose-sm">\{\{\{ marked.parse(block.content) \}\}\}</div>
@end
@if(block.items)
@each(item in block.items)
@if(item.kind === 'image' && item.imageUrl)
<div class="mt-4">
<img src="\{\{ item.imageUrl \}\}" alt="" class="w-full max-w-xl h-auto rounded">
</div>
@end
@if(item.kind === 'video' && item.video && item.video.url)
<div class="mt-4 w-full max-w-xl">
@!component('components/video', {url: item.video.url, librayId: librayId})
</div>
@end
@if(item.kind === 'scripture' && item.scripture)
<div class="mt-4">
@!component('components/scripture', {passage: item.scripture})
</div>
@end
@endeach
@end
@end
</div>
@end
@endeach
@end
</section>
11 changes: 10 additions & 1 deletion src/backend/stubs/validators/story_validator.stub
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
exports({ to: app.makePath('app/validators/story_validator.ts') })
}}}
import vine, { SimpleMessagesProvider } from '@vinejs/vine';
import type { StorySpec, ValidatorType } from '@story-cms/kit';
import {
DevotionDraftValidator,
isDevotionTemplate,
type StorySpec,
type ValidatorType,
} from '@story-cms/kit';

// to save us from setting vine.string().trim().minLength(1) everywhere
vine.convertEmptyStringsToNull = true;
Expand Down Expand Up @@ -41,6 +46,10 @@ export default class Story1 implements ValidatorType {
}

export const storyValidator = (story: StorySpec): ValidatorType => {
if (isDevotionTemplate(story.template)) {
return new DevotionDraftValidator();
}

switch (story.id) {
case 1:
return new Story1();
Expand Down
Loading