;
+ };
+ let findOne: sinon.SinonStub;
+
+ beforeEach(function () {
+ logging = { info: sinon.stub(), warn: sinon.stub(), error: sinon.stub() };
+ sentry = { captureException: sinon.stub() };
+ events = { emit: sinon.stub() };
+ memberModel = {
+ attributes: {
+ id: 'member-id',
+ email: 'member@example.com',
+ status: 'free',
+ },
+ };
+ findOne = sinon.stub().resolves(memberModel);
+ });
+
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ function createDeps(trx: unknown) {
+ return {
+ db: { knex: { transaction: (fn: (trx: unknown) => unknown) => fn(trx) } as never },
+ models: { Member: { findOne } } as never,
+ events,
+ logging,
+ sentry,
+ };
+ }
+
+ it('deletes expired rows, updates comped members and records status events in one unit', async function () {
+ const { trx, updateCalls, insertCalls, deleteCalls, forUpdateCalls, ops } = createTrx({
+ expiredRows: [{ id: 'expired-product-relation-id', member_id: 'member-id' }],
+ compedMembers: [{ id: 'member-id', status: 'comped', updated_at: previousUpdatedAt }],
+ });
+
+ const result = await cleanExpiredComped(createDeps(trx));
+
+ assert.deepEqual(deleteCalls, [
+ { tableName: 'members_products', ids: ['expired-product-relation-id'] },
+ ]);
+
+ // Only the affected members, filtered to those still comped, are read -
+ // and read with a row lock taken before the update, so a concurrent
+ // status change cannot slip in between the read and the update. The ops
+ // assertion pins the whole write sequence of the transaction.
+ assert.deepEqual(forUpdateCalls, [
+ { tableName: 'members', whereInField: 'id', ids: ['member-id'], where: ['status', 'comped'] },
+ ]);
+ assert.deepEqual(ops, [
+ 'forUpdate:members',
+ 'del:members_products',
+ 'update:members',
+ 'insert:members_status_events',
+ ]);
+
+ assert.equal(updateCalls.length, 1);
+ assert.equal(updateCalls[0]!.tableName, 'members');
+ assert.deepEqual(updateCalls[0]!.ids, ['member-id']);
+ assert.equal(updateCalls[0]!.data.status, 'free');
+ assert.ok(updateCalls[0]!.data.updated_at instanceof Date);
+
+ // Status event shares the same timestamp as the member update (not a raw CURRENT_TIMESTAMP)
+ const statusEventInsert = insertCalls.find(
+ (call) => call.tableName === 'members_status_events',
+ );
+ assert.ok(statusEventInsert);
+ const statusEvent = statusEventInsert!.rows![0];
+ assert.equal(typeof statusEvent.id, 'string');
+ assert.equal(statusEvent.member_id, 'member-id');
+ assert.equal(statusEvent.from_status, 'comped');
+ assert.equal(statusEvent.to_status, 'free');
+ assert.deepEqual(statusEvent.created_at, updateCalls[0]!.data.updated_at);
+
+ assert.deepEqual(result, {
+ deletedSubscriptionCount: 1,
+ updatedMemberCount: 1,
+ emittedEventCount: 1,
+ });
+ });
+
+ it('emits a member.edited model event per updated member after the transaction', async function () {
+ const { trx, updateCalls } = createTrx({
+ expiredRows: [{ id: 'expired-product-relation-id', member_id: 'member-id' }],
+ compedMembers: [{ id: 'member-id', status: 'comped', updated_at: previousUpdatedAt }],
+ });
+
+ await cleanExpiredComped(createDeps(trx));
+
+ sinon.assert.calledOnceWithExactly(
+ findOne,
+ { id: 'member-id' },
+ { require: true, context: { internal: true } },
+ );
+
+ sinon.assert.calledOnce(events.emit);
+ const [eventName, emittedModel, options] = events.emit.firstCall.args;
+ assert.equal(eventName, 'member.edited');
+ assert.equal(emittedModel, memberModel);
+ assert.equal(emittedModel._previousAttributes!.status, 'comped');
+ assert.deepEqual(emittedModel._previousAttributes!.updated_at, previousUpdatedAt);
+ assert.equal(emittedModel._changed!.status, 'free');
+ assert.deepEqual(emittedModel._changed!.updated_at, updateCalls[0]!.data.updated_at);
+ assert.deepEqual(options, { context: { internal: true } });
+ });
+
+ it('does nothing when no comped subscriptions have expired', async function () {
+ const { trx, updateCalls, insertCalls, deleteCalls } = createTrx({
+ expiredRows: [],
+ compedMembers: [],
+ });
+
+ const result = await cleanExpiredComped(createDeps(trx));
+
+ assert.equal(deleteCalls.length, 0);
+ assert.equal(updateCalls.length, 0);
+ assert.equal(insertCalls.length, 0);
+ sinon.assert.notCalled(events.emit);
+ assert.deepEqual(result, {
+ deletedSubscriptionCount: 0,
+ updatedMemberCount: 0,
+ emittedEventCount: 0,
+ });
+ });
+
+ it('does not emit model events when the transaction fails', async function () {
+ const { trx } = createTrx({
+ expiredRows: [{ id: 'expired-product-relation-id', member_id: 'member-id' }],
+ compedMembers: [{ id: 'member-id', status: 'comped', updated_at: previousUpdatedAt }],
+ onDelete: () => {
+ throw new Error('database has gone away');
+ },
+ });
+
+ await assert.rejects(() => cleanExpiredComped(createDeps(trx)), /database has gone away/);
+
+ sinon.assert.notCalled(events.emit);
+ });
+
+ it('skips the model event with a warning when the member no longer exists', async function () {
+ const { trx } = createTrx({
+ expiredRows: [{ id: 'expired-product-relation-id', member_id: 'member-id' }],
+ compedMembers: [{ id: 'member-id', status: 'comped', updated_at: previousUpdatedAt }],
+ });
+ findOne.rejects({ errorType: 'NotFoundError' });
+
+ const result = await cleanExpiredComped(createDeps(trx));
+
+ sinon.assert.notCalled(events.emit);
+ sinon.assert.calledOnce(logging.warn);
+ assert.equal(result.updatedMemberCount, 1);
+ assert.equal(result.emittedEventCount, 0);
+ });
+
+ it('logs and reports an unexpected model event failure without failing the job', async function () {
+ const { trx } = createTrx({
+ expiredRows: [{ id: 'expired-product-relation-id', member_id: 'member-id' }],
+ compedMembers: [{ id: 'member-id', status: 'comped', updated_at: previousUpdatedAt }],
+ });
+ const failure = new Error('events bus is broken');
+ events.emit.throws(failure);
+
+ const result = await cleanExpiredComped(createDeps(trx));
+
+ sinon.assert.calledWithExactly(logging.error, failure);
+ sinon.assert.calledWithExactly(sentry.captureException, failure);
+ assert.equal(result.emittedEventCount, 0);
+ });
+
+ it('logs a structured clean_expired_comped.completed event', async function () {
+ const { trx } = createTrx({
+ expiredRows: [{ id: 'expired-product-relation-id', member_id: 'member-id' }],
+ compedMembers: [{ id: 'member-id', status: 'comped', updated_at: previousUpdatedAt }],
+ });
+
+ await cleanExpiredComped(createDeps(trx));
+
+ const completionLog = logging.info.getCalls().find((call) => {
+ return call.args[0]?.system?.event === 'clean_expired_comped.completed';
+ });
+ assert.ok(completionLog, 'the task logs a structured clean_expired_comped.completed event');
+ const { system } = completionLog!.args[0];
+ assert.equal(system.deleted_subscription_count, 1);
+ assert.equal(system.updated_member_count, 1);
+ assert.equal(system.emitted_event_count, 1);
+ assert.equal(typeof system.duration_ms, 'number');
+ });
+});
diff --git a/ghost/core/test/unit/server/services/members/jobs/schedule-expired-comped-cleanup.test.ts b/ghost/core/test/unit/server/services/members/jobs/schedule-expired-comped-cleanup.test.ts
new file mode 100644
index 00000000000..af77aaf93e4
--- /dev/null
+++ b/ghost/core/test/unit/server/services/members/jobs/schedule-expired-comped-cleanup.test.ts
@@ -0,0 +1,59 @@
+import assert from 'node:assert/strict';
+import sinon from 'sinon';
+import { describe, it, beforeEach, afterEach } from 'vitest';
+import logging from '@tryghost/logging';
+
+// require, not import: these must resolve to the same CommonJS module
+// instances that core/server/services/members/jobs/index.js loads, so the
+// init() here is the instance scheduleExpiredCompCleanupJob() reads.
+const jobsService = require('../../../../../../core/server/services/jobs-service');
+const adapterManager = require('../../../../../../core/server/services/adapter-manager').default;
+const memberJobs = require('../../../../../../core/server/services/members/jobs');
+
+describe('member jobs: expired comped cleanup scheduling', function () {
+ let scheduleStub: sinon.SinonStub;
+
+ beforeEach(function () {
+ jobsService.init();
+ const backend = adapterManager.getAdapter('jobs');
+ scheduleStub = sinon.stub(backend, 'scheduleRecurring');
+ });
+
+ afterEach(async function () {
+ await jobsService.shutdown({ timeoutMs: 100 });
+ sinon.restore();
+ });
+
+ it('does not schedule expired comped cleanup under the test environment', async function () {
+ await memberJobs.scheduleExpiredCompCleanupJob();
+
+ assert.ok(
+ scheduleStub.notCalled,
+ 'expired comped cleanup must not be scheduled under NODE_ENV=test*',
+ );
+ });
+
+ it('schedules a single daily off-peak clean-expired-comped job outside the test environment', async function () {
+ const originalEnv = process.env.NODE_ENV;
+ sinon.stub(logging, 'info');
+ process.env.NODE_ENV = 'production';
+ try {
+ await memberJobs.scheduleExpiredCompCleanupJob();
+ await memberJobs.scheduleExpiredCompCleanupJob();
+ } finally {
+ process.env.NODE_ENV = originalEnv;
+ }
+
+ assert.ok(
+ scheduleStub.calledOnce,
+ 'clean-expired-comped is scheduled once, however often scheduling is attempted',
+ );
+ const [envelope, schedule] = scheduleStub.firstCall.args;
+ assert.equal(envelope.type, 'clean-expired-comped');
+ assert.match(
+ schedule.cron,
+ /^\d{1,2} \d{1,2} [0-5] \* \* \*$/,
+ 'a random daily cron inside the 0-5am off-peak window',
+ );
+ });
+});
diff --git a/ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts b/ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts
index 02d88cbdb10..b976b14c7ab 100644
--- a/ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts
+++ b/ghost/core/test/unit/server/services/route-settings/validate-route-settings.test.ts
@@ -712,7 +712,7 @@ describe('UNIT: services/route-settings/validation (via parseRouteSettings)', fu
it('points at the docs for schema failures and at an example for data failures', function () {
assert.equal(
helpFor({ routes: { '/x/': { controller: 'channel', filter: 42 } } }),
- 'https://ghost.org/docs/themes/routing/',
+ 'https://docs.ghost.org/themes/routing/',
);
assert.equal(
helpFor({ routes: { '/x/': { template: 'x', data: 'nonsense' } } }),
diff --git a/ghost/core/test/unit/server/services/route-settings/validation-errors.test.ts b/ghost/core/test/unit/server/services/route-settings/validation-errors.test.ts
index 31c2dd69fcf..99ff0945d5d 100644
--- a/ghost/core/test/unit/server/services/route-settings/validation-errors.test.ts
+++ b/ghost/core/test/unit/server/services/route-settings/validation-errors.test.ts
@@ -62,7 +62,7 @@ describe('UNIT: services/route-settings/validation-errors', function () {
err.message,
/^The following definition "routes\['\/x\/'\]\.mystery" is invalid: /,
);
- assert.equal(err.help, 'https://ghost.org/docs/themes/routing/');
+ assert.equal(err.help, 'https://docs.ghost.org/themes/routing/');
});
it('does not mistake inherited object properties for known keys', function () {
diff --git a/ghost/core/test/unit/server/services/themes/validate.test.js b/ghost/core/test/unit/server/services/themes/validate.test.js
index a9005ea3bde..93175e01bba 100644
--- a/ghost/core/test/unit/server/services/themes/validate.test.js
+++ b/ghost/core/test/unit/server/services/themes/validate.test.js
@@ -92,7 +92,7 @@ describe('Themes', function () {
level: 'error',
rule: 'Replace the {{#if author.cover}} helper with {{#if author.cover_image}}',
details:
- 'The cover attribute was replaced with cover_image. Instead of {{#if author.cover}} you need to use {{#if author.cover_image}}. See the object attributes of author here .',
+ 'The cover attribute was replaced with cover_image. Instead of {{#if author.cover}} you need to use {{#if author.cover_image}}. See the object attributes of author here .',
failures: [{}],
code: 'GS001-DEPR-CON-AC',
},
@@ -121,7 +121,7 @@ describe('Themes', function () {
level: 'error',
rule: 'Replace the {{#if author.cover}} helper with {{#if author.cover_image}}',
details:
- 'The cover attribute was replaced with cover_image. Instead of {{#if author.cover}} you need to use {{#if author.cover_image}}. See the object attributes of author here .',
+ 'The cover attribute was replaced with cover_image. Instead of {{#if author.cover}} you need to use {{#if author.cover_image}}. See the object attributes of author here .',
failures: [{}],
code: 'GS001-DEPR-CON-AC',
},
diff --git a/ghost/core/test/unit/server/services/update-check.test.js b/ghost/core/test/unit/server/services/update-check.test.js
index f3b02d9fd91..3c939691bbc 100644
--- a/ghost/core/test/unit/server/services/update-check.test.js
+++ b/ghost/core/test/unit/server/services/update-check.test.js
@@ -647,7 +647,7 @@ describe('Update Check', function () {
);
assert.equal(
logging.error.args[0][0].err.help,
- 'If you get this error repeatedly, please seek help from https://ghost.org/docs/',
+ 'If you get this error repeatedly, please seek help from https://docs.ghost.org/',
);
});
@@ -675,7 +675,7 @@ describe('Update Check', function () {
);
assert.equal(
logging.error.args[0][0].err.help,
- 'If you get this error repeatedly, please seek help from https://ghost.org/docs/',
+ 'If you get this error repeatedly, please seek help from https://docs.ghost.org/',
);
assert.equal(
diff --git a/ghost/core/test/utils/fixtures/export/v2_export.json b/ghost/core/test/utils/fixtures/export/v2_export.json
index 0d1f318d797..fa53baf50dd 100644
--- a/ghost/core/test/utils/fixtures/export/v2_export.json
+++ b/ghost/core/test/utils/fixtures/export/v2_export.json
@@ -1931,10 +1931,10 @@
"uuid": "4b2913ec-daf4-4eb3-8128-17dbec146be8",
"title": "Organising your content",
"slug": "organising-content",
- "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[[\"soft-return\",\"\",{}]],\"cards\":[],\"markups\":[[\"strong\"],[\"code\"],[\"em\"],[\"a\",[\"href\",\"https://themes.ghost.org/v2.0.0/docs\"]],[\"a\",[\"href\",\"http://yaml.org/spec/1.2/spec.html\",\"rel\",\"noreferrer nofollow noopener\"]],[\"a\",[\"href\",\"https://docs.ghost.org/docs/dynamic-routing\"]],[\"a\",[\"href\",\"/apps-integrations/\"]]],\"sections\":[[1,\"p\",[[0,[],0,\"Ghost has a flexible organisational taxonomy called\"],[0,[0],1,\" tags\"],[0,[],0,\" which can be used to configure your site structure using \"],[0,[0],1,\"dynamic routing\"],[0,[],0,\". \"]]],[1,\"h1\",[[0,[],0,\"Basic Tagging\"]]],[1,\"p\",[[0,[],0,\"You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.\"]]],[1,\"p\",[[0,[],0,\"When you create content for your publication you can assign tags to help differentiate between categories of content. \"]]],[1,\"p\",[[0,[],0,\"For example you may tag some content with News and other content with Podcast, which would create two distinct categories of content listed on \"],[0,[1],1,\"/tag/news/\"],[0,[],0,\" and \"],[0,[1],1,\"/tag/weather/\"],[0,[],0,\", respectively.\"]]],[1,\"p\",[[0,[],0,\"If you tag a post with both \"],[0,[1],1,\"News\"],[0,[],0,\" \"],[0,[2],1,\"and\"],[0,[],0,\" \"],[0,[1],1,\"Weather\"],[0,[],0,\" - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.\"]]],[1,\"h1\",[[0,[],0,\"The primary tag\"]]],[1,\"p\",[[0,[],0,\"Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default. \"]]],[1,\"blockquote\",[[0,[2,0],1,\"News\"],[0,[],1,\", Technology, Startup\"]]],[1,\"p\",[[0,[],0,\"So you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.\"]]],[1,\"h1\",[[0,[],0,\"Private tags\"]]],[1,\"p\",[[0,[],0,\"Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.\"]]],[1,\"p\",[[0,[],0,\"For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.\"]]],[1,\"blockquote\",[[0,[2,0],1,\"News\"],[0,[],1,\", #video\"]]],[1,\"p\",[[0,[],0,\"Here, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting. \"]]],[1,\"blockquote\",[[0,[2],0,\"You can find documentation for theme development techniques like this and many more over on Ghost's extensive \"],[0,[3],1,\"theme documentation\"],[0,[],1,\".\"]]],[1,\"h1\",[[0,[],0,\"Dynamic Routing\"]]],[1,\"p\",[[0,[],0,\"Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates. \"]]],[1,\"p\",[[0,[],0,\"For example, you may not want content tagged with \"],[0,[1],1,\"News\"],[0,[],0,\" to exist on: \"],[0,[1],1,\"example.com/tag/news\"],[0,[],0,\". Instead, you want it to exist on \"],[0,[1],1,\"example.com/news\"],[0,[],0,\" . \"]]],[1,\"p\",[[0,[],0,\"In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.\"]]],[1,\"p\",[[0,[],0,\"There are lots of use cases for dynamic routing with Ghost, here are a few common examples: \"]]],[3,\"ul\",[[[0,[],0,\"Setting a custom home page with its own template\"]],[[0,[],0,\"Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content\"]],[[0,[],0,\"Creating a founders column as a unique view, by filtering content created by specific authors\"]],[[0,[],0,\"Including dates in permalinks for your posts\"]],[[0,[],0,\"Setting posts to have a URL relative to their primary tag like \"],[0,[1],1,\"example.com/europe/story-title/\"],[1,[],0,0]]]],[1,\"blockquote\",[[0,[2],0,\"Dynamic routing can be configured in Ghost using \"],[0,[4],1,\"YAML\"],[0,[],0,\" files. Read our dynamic routing \"],[0,[5],1,\"documentation\"],[0,[],1,\" for further details.\"]]],[1,\"p\",[[0,[],0,\"You can further customise your site using \"],[0,[6],1,\"Apps & Integrations\"],[0,[],0,\".\"]]]]}",
- "html": "Ghost has a flexible organisational taxonomy called tags which can be used to configure your site structure using dynamic routing .
Basic Tagging You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.
When you create content for your publication you can assign tags to help differentiate between categories of content.
For example you may tag some content with ย News and other content with Podcast, which would create two distinct categories of content listed on /tag/news/ and /tag/weather/, respectively.
If you tag a post with both News and Weather - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.
The primary tag Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default.
News , Technology, StartupSo you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.
Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.
For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.
News , #videoHere, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting.
You can find documentation for theme development techniques like this and many more over on Ghost's extensive theme documentation . Dynamic Routing Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates.
For example, you may not want content tagged with News to exist on: example.com/tag/news. Instead, you want it to exist on example.com/news .
In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.
There are lots of use cases for dynamic routing with Ghost, here are a few common examples:
Setting a custom home page with its own template Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content Creating a founders column as a unique view, by filtering content created by specific authors Including dates in permalinks for your posts Setting posts to have a URL relative to their primary tag like example.com/europe/story-title/ Dynamic routing can be configured in Ghost using YAML files. Read our dynamic routing documentation for further details. You can further customise your site using Apps & Integrations .
",
+ "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[[\"soft-return\",\"\",{}]],\"cards\":[],\"markups\":[[\"strong\"],[\"code\"],[\"em\"],[\"a\",[\"href\",\"https://themes.ghost.org/v2.0.0/docs\"]],[\"a\",[\"href\",\"http://yaml.org/spec/1.2/spec.html\",\"rel\",\"noreferrer nofollow noopener\"]],[\"a\",[\"href\",\"https://docs.docs.ghost.org/dynamic-routing\"]],[\"a\",[\"href\",\"/apps-integrations/\"]]],\"sections\":[[1,\"p\",[[0,[],0,\"Ghost has a flexible organisational taxonomy called\"],[0,[0],1,\" tags\"],[0,[],0,\" which can be used to configure your site structure using \"],[0,[0],1,\"dynamic routing\"],[0,[],0,\". \"]]],[1,\"h1\",[[0,[],0,\"Basic Tagging\"]]],[1,\"p\",[[0,[],0,\"You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.\"]]],[1,\"p\",[[0,[],0,\"When you create content for your publication you can assign tags to help differentiate between categories of content. \"]]],[1,\"p\",[[0,[],0,\"For example you may tag some content with News and other content with Podcast, which would create two distinct categories of content listed on \"],[0,[1],1,\"/tag/news/\"],[0,[],0,\" and \"],[0,[1],1,\"/tag/weather/\"],[0,[],0,\", respectively.\"]]],[1,\"p\",[[0,[],0,\"If you tag a post with both \"],[0,[1],1,\"News\"],[0,[],0,\" \"],[0,[2],1,\"and\"],[0,[],0,\" \"],[0,[1],1,\"Weather\"],[0,[],0,\" - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.\"]]],[1,\"h1\",[[0,[],0,\"The primary tag\"]]],[1,\"p\",[[0,[],0,\"Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default. \"]]],[1,\"blockquote\",[[0,[2,0],1,\"News\"],[0,[],1,\", Technology, Startup\"]]],[1,\"p\",[[0,[],0,\"So you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.\"]]],[1,\"h1\",[[0,[],0,\"Private tags\"]]],[1,\"p\",[[0,[],0,\"Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.\"]]],[1,\"p\",[[0,[],0,\"For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.\"]]],[1,\"blockquote\",[[0,[2,0],1,\"News\"],[0,[],1,\", #video\"]]],[1,\"p\",[[0,[],0,\"Here, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting. \"]]],[1,\"blockquote\",[[0,[2],0,\"You can find documentation for theme development techniques like this and many more over on Ghost's extensive \"],[0,[3],1,\"theme documentation\"],[0,[],1,\".\"]]],[1,\"h1\",[[0,[],0,\"Dynamic Routing\"]]],[1,\"p\",[[0,[],0,\"Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates. \"]]],[1,\"p\",[[0,[],0,\"For example, you may not want content tagged with \"],[0,[1],1,\"News\"],[0,[],0,\" to exist on: \"],[0,[1],1,\"example.com/tag/news\"],[0,[],0,\". Instead, you want it to exist on \"],[0,[1],1,\"example.com/news\"],[0,[],0,\" . \"]]],[1,\"p\",[[0,[],0,\"In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.\"]]],[1,\"p\",[[0,[],0,\"There are lots of use cases for dynamic routing with Ghost, here are a few common examples: \"]]],[3,\"ul\",[[[0,[],0,\"Setting a custom home page with its own template\"]],[[0,[],0,\"Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content\"]],[[0,[],0,\"Creating a founders column as a unique view, by filtering content created by specific authors\"]],[[0,[],0,\"Including dates in permalinks for your posts\"]],[[0,[],0,\"Setting posts to have a URL relative to their primary tag like \"],[0,[1],1,\"example.com/europe/story-title/\"],[1,[],0,0]]]],[1,\"blockquote\",[[0,[2],0,\"Dynamic routing can be configured in Ghost using \"],[0,[4],1,\"YAML\"],[0,[],0,\" files. Read our dynamic routing \"],[0,[5],1,\"documentation\"],[0,[],1,\" for further details.\"]]],[1,\"p\",[[0,[],0,\"You can further customise your site using \"],[0,[6],1,\"Apps & Integrations\"],[0,[],0,\".\"]]]]}",
+ "html": "Ghost has a flexible organisational taxonomy called tags which can be used to configure your site structure using dynamic routing .
Basic Tagging You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.
When you create content for your publication you can assign tags to help differentiate between categories of content.
For example you may tag some content with ย News and other content with Podcast, which would create two distinct categories of content listed on /tag/news/ and /tag/weather/, respectively.
If you tag a post with both News and Weather - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.
The primary tag Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default.
News , Technology, StartupSo you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.
Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.
For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.
News , #videoHere, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting.
You can find documentation for theme development techniques like this and many more over on Ghost's extensive theme documentation . Dynamic Routing Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates.
For example, you may not want content tagged with News to exist on: example.com/tag/news. Instead, you want it to exist on example.com/news .
In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.
There are lots of use cases for dynamic routing with Ghost, here are a few common examples:
Setting a custom home page with its own template Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content Creating a founders column as a unique view, by filtering content created by specific authors Including dates in permalinks for your posts Setting posts to have a URL relative to their primary tag like example.com/europe/story-title/ Dynamic routing can be configured in Ghost using YAML files. Read our dynamic routing documentation for further details. You can further customise your site using Apps & Integrations .
",
"comment_id": "605ad4e57b12e2d97db5d779",
- "plaintext": "Ghost has a flexible organisational taxonomy called tags which can be used to\nconfigure your site structure using dynamic routing. \n\nBasic Tagging\nYou can think of tags like Gmail labels. By tagging posts with one or more\nkeyword, you can organise articles into buckets of related content.\n\nWhen you create content for your publication you can assign tags to help\ndifferentiate between categories of content. \n\nFor example you may tag some content with ย News and other content with Podcast,\nwhich would create two distinct categories of content listed on /tag/news/ and \n/tag/weather/, respectively.\n\nIf you tag a post with both News and Weather - then it appears in both\nsections. Tag archives are like dedicated home-pages for each category of\ncontent that you have. They have their own pages, their own RSS feeds, and can\nsupport their own cover images and meta data.\n\nThe primary tag\nInside the Ghost editor, you can drag and drop tags into a specific order. The\nfirst tag in the list is always given the most importance, and some themes will\nonly display the primary tag (the first tag in the list) by default. \n\nNews, Technology, StartupSo you can add the most important tag which you want to\nshow up in your theme, but also add related tags which are less important.\n\nPrivate tags\nSometimes you may want to assign a post a specific tag, but you don't\nnecessarily want that tag appearing in the theme or creating an archive page. In\nGhost, hashtags are private and can be used for special styling.\n\nFor example, if you sometimes publish posts with video content - you might want\nyour theme to adapt and get rid of the sidebar for these posts, to give more\nspace for an embedded video to fill the screen. In this case, you could use\nprivate tags to tell your theme what to do.\n\nNews, #videoHere, the theme would assign the post publicly displayed tags of\nNews - but it would also keep a private record of the post being tagged with\n#video. In your theme, you could then look for private tags conditionally and\ngive them special formatting. \n\nYou can find documentation for theme development techniques like this and many\nmore over on Ghost's extensive theme documentation\n[https://themes.ghost.org/v2.0.0/docs].Dynamic Routing\nDynamic routing gives you the ultimate freedom to build a custom publication to\nsuit your needs. Routes are rules that map URL patterns to your content and\ntemplates. \n\nFor example, you may not want content tagged with News to exist on: \nexample.com/tag/news. Instead, you want it to exist on example.com/news . \n\nIn this case you can use dynamic routes to create customised collections of\ncontent on your site. It's also possible to use multiple templates in your theme\nto render each content type differently.\n\nThere are lots of use cases for dynamic routing with Ghost, here are a few\ncommon examples: \n\n * Setting a custom home page with its own template\n * Having separate content hubs for blog and podcast, that render differently,\n and have custom RSS feeds to support two types of content\n * Creating a founders column as a unique view, by filtering content created by\n specific authors\n * Including dates in permalinks for your posts\n * Setting posts to have a URL relative to their primary tag like \n example.com/europe/story-title/\n \n\nDynamic routing can be configured in Ghost using YAML\n[http://yaml.org/spec/1.2/spec.html] files. Read our dynamic routing \ndocumentation [https://docs.ghost.org/docs/dynamic-routing] for further\ndetails.You can further customise your site using Apps & Integrations\n[/apps-integrations/].",
+ "plaintext": "Ghost has a flexible organisational taxonomy called tags which can be used to\nconfigure your site structure using dynamic routing. \n\nBasic Tagging\nYou can think of tags like Gmail labels. By tagging posts with one or more\nkeyword, you can organise articles into buckets of related content.\n\nWhen you create content for your publication you can assign tags to help\ndifferentiate between categories of content. \n\nFor example you may tag some content with ย News and other content with Podcast,\nwhich would create two distinct categories of content listed on /tag/news/ and \n/tag/weather/, respectively.\n\nIf you tag a post with both News and Weather - then it appears in both\nsections. Tag archives are like dedicated home-pages for each category of\ncontent that you have. They have their own pages, their own RSS feeds, and can\nsupport their own cover images and meta data.\n\nThe primary tag\nInside the Ghost editor, you can drag and drop tags into a specific order. The\nfirst tag in the list is always given the most importance, and some themes will\nonly display the primary tag (the first tag in the list) by default. \n\nNews, Technology, StartupSo you can add the most important tag which you want to\nshow up in your theme, but also add related tags which are less important.\n\nPrivate tags\nSometimes you may want to assign a post a specific tag, but you don't\nnecessarily want that tag appearing in the theme or creating an archive page. In\nGhost, hashtags are private and can be used for special styling.\n\nFor example, if you sometimes publish posts with video content - you might want\nyour theme to adapt and get rid of the sidebar for these posts, to give more\nspace for an embedded video to fill the screen. In this case, you could use\nprivate tags to tell your theme what to do.\n\nNews, #videoHere, the theme would assign the post publicly displayed tags of\nNews - but it would also keep a private record of the post being tagged with\n#video. In your theme, you could then look for private tags conditionally and\ngive them special formatting. \n\nYou can find documentation for theme development techniques like this and many\nmore over on Ghost's extensive theme documentation\n[https://themes.ghost.org/v2.0.0/docs].Dynamic Routing\nDynamic routing gives you the ultimate freedom to build a custom publication to\nsuit your needs. Routes are rules that map URL patterns to your content and\ntemplates. \n\nFor example, you may not want content tagged with News to exist on: \nexample.com/tag/news. Instead, you want it to exist on example.com/news . \n\nIn this case you can use dynamic routes to create customised collections of\ncontent on your site. It's also possible to use multiple templates in your theme\nto render each content type differently.\n\nThere are lots of use cases for dynamic routing with Ghost, here are a few\ncommon examples: \n\n * Setting a custom home page with its own template\n * Having separate content hubs for blog and podcast, that render differently,\n and have custom RSS feeds to support two types of content\n * Creating a founders column as a unique view, by filtering content created by\n specific authors\n * Including dates in permalinks for your posts\n * Setting posts to have a URL relative to their primary tag like \n example.com/europe/story-title/\n \n\nDynamic routing can be configured in Ghost using YAML\n[http://yaml.org/spec/1.2/spec.html] files. Read our dynamic routing \ndocumentation [https://docs.docs.ghost.org/dynamic-routing] for further\ndetails.You can further customise your site using Apps & Integrations\n[/apps-integrations/].",
"feature_image": "https://static.ghost.org/v2.0.0/images/organising-your-content.jpg",
"featured": 0,
"page": 0,
diff --git a/ghost/core/test/utils/fixtures/export/v3_export.json b/ghost/core/test/utils/fixtures/export/v3_export.json
index 80f19bf1fbf..db4ef1bb52b 100644
--- a/ghost/core/test/utils/fixtures/export/v3_export.json
+++ b/ghost/core/test/utils/fixtures/export/v3_export.json
@@ -2000,10 +2000,10 @@
"uuid": "267e7f1c-c98f-4403-aa38-e4c9931ec86e",
"title": "Creating a custom theme",
"slug": "themes",
- "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[[\"soft-return\",\"\",{}]],\"cards\":[[\"image\",{\"src\":\"https://static.ghost.org/v3.0.0/images/theme-marketplace.png\",\"caption\":\"Anyone can write a completely custom Ghost theme with some solid knowledge of HTML and CSS\",\"alt\":\"Ghost theme marketplace screenshot\"}]],\"markups\":[[\"a\",[\"href\",\"https://ghost.org/marketplace/\"]],[\"code\"],[\"a\",[\"href\",\"https://github.com/TryGhost/Casper\"]],[\"a\",[\"href\",\"https://ghost.org/docs/api/handlebars-themes/\"]],[\"a\",[\"href\",\"https://github.com/TryGhost/Starter/\"]],[\"strong\"],[\"a\",[\"href\",\"https://forum.ghost.org/c/themes\"]]],\"sections\":[[1,\"h2\",[[0,[],0,\"Ghost themes\"]]],[1,\"p\",[[0,[],0,\"Ghost comes with a default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes.\"]]],[1,\"p\",[[0,[],0,\"If you need something a little more customised, it's entirely possible to build on top of existing open source themes, or to build your own from scratch. Rather than giving you a few basic settings which act as a poor proxy for code, we just let you write code.\"]]],[1,\"h2\",[[0,[],0,\"Marketplace\"]]],[1,\"p\",[[0,[],0,\"There are a huge range of both free and premium pre-built themes which you can download from the \"],[0,[0],1,\"Ghost Theme Marketplace\"],[0,[],0,\":\"]]],[10,0],[1,\"h2\",[[0,[],0,\"Theme development\"]]],[1,\"p\",[[0,[],0,\"Ghost themes are written with a templating language called handlebars, which has a set of dynamic helpers to insert your data into template files. For example: \"],[0,[1],1,\"{{author.name}}\"],[0,[],0,\" outputs the name of the current author.\"]]],[1,\"p\",[[0,[],0,\"The best way to learn how to write your own Ghost theme is to have a look at \"],[0,[2],1,\"the source code for Casper\"],[0,[],0,\", which is heavily commented and should give you a sense of how everything fits together.\"],[1,[],0,0]]],[3,\"ul\",[[[0,[1],1,\"default.hbs\"],[0,[],0,\" is the main template file, all contexts will load inside this file unless specifically told to use a different template.\"]],[[0,[1],1,\"post.hbs\"],[0,[],0,\" is the file used in the context of viewing a post.\"]],[[0,[1],1,\"index.hbs\"],[0,[],0,\" is the file u\"]],[[0,[],0,\"cused in the context of viewing the home page.\"]],[[0,[],0,\"and so on\"]]]],[1,\"p\",[[0,[],0,\"We've got \"],[0,[3],1,\"full and extensive theme documentation\"],[0,[],0,\" which outlines every template file, context and helper that you can use. You can also get started with our useful \"],[0,[4],1,\"starter theme\"],[0,[],0,\", which includes the most common foundations and components required to build your own theme.\"]]],[1,\"blockquote\",[[0,[],0,\"If you want to chat with other people making Ghost themes to get any advice or help, there's also a \"],[0,[5],1,\"themes\"],[0,[],0,\" section on our \"],[0,[6],1,\"public Ghost forum\"],[0,[],0,\".\"]]]]}",
- "html": "Ghost themes Ghost comes with a default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes.
If you need something a little more customised, it's entirely possible to build on top of existing open source themes, or to build your own from scratch. Rather than giving you a few basic settings which act as a poor proxy for code, we just let you write code.
Marketplace There are a huge range of both free and premium pre-built themes which you can download from the Ghost Theme Marketplace :
Anyone can write a completely custom Ghost theme with some solid knowledge of HTML and CSS Theme development Ghost themes are written with a templating language called handlebars, which has a set of dynamic helpers to insert your data into template files. For example: {{author.name}} outputs the name of the current author.
The best way to learn how to write your own Ghost theme is to have a look at the source code for Casper , which is heavily commented and should give you a sense of how everything fits together.
default.hbs is the main template file, all contexts will load inside this file unless specifically told to use a different template.post.hbs is the file used in the context of viewing a post.index.hbs is the file ucused in the context of viewing the home page. and so on We've got full and extensive theme documentation which outlines every template file, context and helper that you can use. You can also get started with our useful starter theme , which includes the most common foundations and components required to build your own theme.
If you want to chat with other people making Ghost themes to get any advice or help, there's also a themes section on our public Ghost forum . ",
+ "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[[\"soft-return\",\"\",{}]],\"cards\":[[\"image\",{\"src\":\"https://static.ghost.org/v3.0.0/images/theme-marketplace.png\",\"caption\":\"Anyone can write a completely custom Ghost theme with some solid knowledge of HTML and CSS\",\"alt\":\"Ghost theme marketplace screenshot\"}]],\"markups\":[[\"a\",[\"href\",\"https://ghost.org/marketplace/\"]],[\"code\"],[\"a\",[\"href\",\"https://github.com/TryGhost/Casper\"]],[\"a\",[\"href\",\"https://docs.ghost.org/api/handlebars-themes/\"]],[\"a\",[\"href\",\"https://github.com/TryGhost/Starter/\"]],[\"strong\"],[\"a\",[\"href\",\"https://forum.ghost.org/c/themes\"]]],\"sections\":[[1,\"h2\",[[0,[],0,\"Ghost themes\"]]],[1,\"p\",[[0,[],0,\"Ghost comes with a default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes.\"]]],[1,\"p\",[[0,[],0,\"If you need something a little more customised, it's entirely possible to build on top of existing open source themes, or to build your own from scratch. Rather than giving you a few basic settings which act as a poor proxy for code, we just let you write code.\"]]],[1,\"h2\",[[0,[],0,\"Marketplace\"]]],[1,\"p\",[[0,[],0,\"There are a huge range of both free and premium pre-built themes which you can download from the \"],[0,[0],1,\"Ghost Theme Marketplace\"],[0,[],0,\":\"]]],[10,0],[1,\"h2\",[[0,[],0,\"Theme development\"]]],[1,\"p\",[[0,[],0,\"Ghost themes are written with a templating language called handlebars, which has a set of dynamic helpers to insert your data into template files. For example: \"],[0,[1],1,\"{{author.name}}\"],[0,[],0,\" outputs the name of the current author.\"]]],[1,\"p\",[[0,[],0,\"The best way to learn how to write your own Ghost theme is to have a look at \"],[0,[2],1,\"the source code for Casper\"],[0,[],0,\", which is heavily commented and should give you a sense of how everything fits together.\"],[1,[],0,0]]],[3,\"ul\",[[[0,[1],1,\"default.hbs\"],[0,[],0,\" is the main template file, all contexts will load inside this file unless specifically told to use a different template.\"]],[[0,[1],1,\"post.hbs\"],[0,[],0,\" is the file used in the context of viewing a post.\"]],[[0,[1],1,\"index.hbs\"],[0,[],0,\" is the file u\"]],[[0,[],0,\"cused in the context of viewing the home page.\"]],[[0,[],0,\"and so on\"]]]],[1,\"p\",[[0,[],0,\"We've got \"],[0,[3],1,\"full and extensive theme documentation\"],[0,[],0,\" which outlines every template file, context and helper that you can use. You can also get started with our useful \"],[0,[4],1,\"starter theme\"],[0,[],0,\", which includes the most common foundations and components required to build your own theme.\"]]],[1,\"blockquote\",[[0,[],0,\"If you want to chat with other people making Ghost themes to get any advice or help, there's also a \"],[0,[5],1,\"themes\"],[0,[],0,\" section on our \"],[0,[6],1,\"public Ghost forum\"],[0,[],0,\".\"]]]]}",
+ "html": "Ghost themes Ghost comes with a default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes.
If you need something a little more customised, it's entirely possible to build on top of existing open source themes, or to build your own from scratch. Rather than giving you a few basic settings which act as a poor proxy for code, we just let you write code.
Marketplace There are a huge range of both free and premium pre-built themes which you can download from the Ghost Theme Marketplace :
Anyone can write a completely custom Ghost theme with some solid knowledge of HTML and CSS Theme development Ghost themes are written with a templating language called handlebars, which has a set of dynamic helpers to insert your data into template files. For example: {{author.name}} outputs the name of the current author.
The best way to learn how to write your own Ghost theme is to have a look at the source code for Casper , which is heavily commented and should give you a sense of how everything fits together.
default.hbs is the main template file, all contexts will load inside this file unless specifically told to use a different template.post.hbs is the file used in the context of viewing a post.index.hbs is the file ucused in the context of viewing the home page. and so on We've got full and extensive theme documentation which outlines every template file, context and helper that you can use. You can also get started with our useful starter theme , which includes the most common foundations and components required to build your own theme.
If you want to chat with other people making Ghost themes to get any advice or help, there's also a themes section on our public Ghost forum . ",
"comment_id": "605a8e13b6cf646d6629d6c2",
- "plaintext": "Ghost themes\nGhost comes with a default theme called Casper, which is designed to be a clean,\nreadable publication layout and can be easily adapted for most purposes.\n\nIf you need something a little more customised, it's entirely possible to build\non top of existing open source themes, or to build your own from scratch. Rather\nthan giving you a few basic settings which act as a poor proxy for code, we just\nlet you write code.\n\nMarketplace\nThere are a huge range of both free and premium pre-built themes which you can\ndownload from the Ghost Theme Marketplace [https://ghost.org/marketplace/]:\n\nAnyone can write a completely custom Ghost theme with some solid knowledge of\nHTML and CSSTheme development\nGhost themes are written with a templating language called handlebars, which has\na set of dynamic helpers to insert your data into template files. For example: \n{{author.name}} outputs the name of the current author.\n\nThe best way to learn how to write your own Ghost theme is to have a look at \nthe\nsource code for Casper [https://github.com/TryGhost/Casper], which is heavily\ncommented and should give you a sense of how everything fits together.\n\n\n * default.hbs is the main template file, all contexts will load inside this\n file unless specifically told to use a different template.\n * post.hbs is the file used in the context of viewing a post.\n * index.hbs is the file u\n * cused in the context of viewing the home page.\n * and so on\n\nWe've got full and extensive theme documentation\n[https://ghost.org/docs/api/handlebars-themes/] which outlines every template\nfile, context and helper that you can use. You can also get started with our\nuseful starter theme [https://github.com/TryGhost/Starter/], which includes the\nmost common foundations and components required to build your own theme.\n\n> If you want to chat with other people making Ghost themes to get any advice or\nhelp, there's also a themes section on our public Ghost forum\n[https://forum.ghost.org/c/themes].",
+ "plaintext": "Ghost themes\nGhost comes with a default theme called Casper, which is designed to be a clean,\nreadable publication layout and can be easily adapted for most purposes.\n\nIf you need something a little more customised, it's entirely possible to build\non top of existing open source themes, or to build your own from scratch. Rather\nthan giving you a few basic settings which act as a poor proxy for code, we just\nlet you write code.\n\nMarketplace\nThere are a huge range of both free and premium pre-built themes which you can\ndownload from the Ghost Theme Marketplace [https://ghost.org/marketplace/]:\n\nAnyone can write a completely custom Ghost theme with some solid knowledge of\nHTML and CSSTheme development\nGhost themes are written with a templating language called handlebars, which has\na set of dynamic helpers to insert your data into template files. For example: \n{{author.name}} outputs the name of the current author.\n\nThe best way to learn how to write your own Ghost theme is to have a look at \nthe\nsource code for Casper [https://github.com/TryGhost/Casper], which is heavily\ncommented and should give you a sense of how everything fits together.\n\n\n * default.hbs is the main template file, all contexts will load inside this\n file unless specifically told to use a different template.\n * post.hbs is the file used in the context of viewing a post.\n * index.hbs is the file u\n * cused in the context of viewing the home page.\n * and so on\n\nWe've got full and extensive theme documentation\n[https://docs.ghost.org/api/handlebars-themes/] which outlines every template\nfile, context and helper that you can use. You can also get started with our\nuseful starter theme [https://github.com/TryGhost/Starter/], which includes the\nmost common foundations and components required to build your own theme.\n\n> If you want to chat with other people making Ghost themes to get any advice or\nhelp, there's also a themes section on our public Ghost forum\n[https://forum.ghost.org/c/themes].",
"feature_image": "https://static.ghost.org/v3.0.0/images/creating-a-custom-theme.png",
"featured": 0,
"type": "post",
@@ -2025,10 +2025,10 @@
"uuid": "91d5c9a5-cc70-446e-930c-5f74540a451e",
"title": "Apps & integrations",
"slug": "apps-integrations",
- "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[[\"image\",{\"src\":\"https://static.ghost.org/v3.0.0/images/integrations-icons.png\",\"cardWidth\":\"full\"}],[\"markdown\",{\"markdown\":\"\\n\"}],[\"image\",{\"src\":\"https://static.ghost.org/v3.0.0/images/integrations-and-webhooks-in-ghost.png\",\"alt\":\"Screenshot of custom integrations with webhooks in Ghost Admin\",\"cardWidth\":\"\"}]],\"markups\":[[\"a\",[\"href\",\"https://ghost.org/integrations/\"]],[\"a\",[\"href\",\"https://zapier.com\"]],[\"strong\"],[\"a\",[\"href\",\"https://ghost.org/docs/api/\"]],[\"a\",[\"href\",\"/themes/\"]]],\"sections\":[[1,\"h2\",[[0,[],0,\"Work with your existing tools\"]]],[1,\"p\",[[0,[],0,\"It's possible to connect your Ghost site to hundreds of the most popular apps and tools using integrations that take no more than a few minutes to setup.\"]]],[1,\"p\",[[0,[],0,\"Whether you need to automate workflows, connect your email list, build a community or embed products from your ecommerce store, our \"],[0,[0],1,\"integrations library\"],[0,[],0,\" has got it all covered with hundreds of tutorials.\"]]],[10,0],[1,\"h2\",[[0,[],0,\"Zapier\"]]],[1,\"p\",[[0,[],0,\"On top of this, you can connect your Ghost site to more than 1,000 external services using the official integration with \"],[0,[1],1,\"Zapier\"],[0,[],0,\".\"]]],[1,\"p\",[[0,[],0,\"Zapier sets up automations with Triggers and Actions, which allows you to create and customise a wide range of connected applications.\"]]],[1,\"blockquote\",[[0,[2],1,\"Example\"],[0,[],0,\": When someone new subscribes to a newsletter on a Ghost site (Trigger) then the contact information is automatically pushed into MailChimp (Action).\"]]],[1,\"p\",[[0,[2],1,\"Here are the most popular Ghost<>Zapier automation templates:\"],[0,[],0,\" \"]]],[10,1],[1,\"h2\",[[0,[],0,\"Custom integrations\"]]],[1,\"p\",[[0,[],0,\"At the heart of Ghost sits a robust JSON API โ designed to create, manage and retrieve content with ease. \"]]],[1,\"p\",[[0,[],0,\"It's possible to create custom Ghost integrations with dedicated API keys and webhooks from the Integrations page within Ghost Admin. \"]]],[10,2],[1,\"p\",[[0,[],0,\"Beyond that, the API allows you to build entirely custom publishing apps. You can send content from your favourite desktop editor, build a custom interface for handling editorial workflow or use Ghost as a full headless CMS with a custom front-end.\"]]],[1,\"p\",[[0,[],0,\"The Ghost API is \"],[0,[3],1,\"thoroughly documented\"],[0,[],0,\" and straightforward to work with for developers of almost any level. \"]]],[1,\"h2\",[[0,[],0,\"Final step: Themes\"]]],[1,\"p\",[[0,[],0,\"Alright, on to the last post in our welcome-series! If you're curious about creating your own Ghost theme from scratch, \"],[0,[4],1,\"find out how that works\"],[0,[],0,\".\"]]]]}",
- "html": "It's possible to connect your Ghost site to hundreds of the most popular apps and tools using integrations that take no more than a few minutes to setup.
Whether you need to automate workflows, connect your email list, build a community or embed products from your ecommerce store, our integrations library has got it all covered with hundreds of tutorials.
Zapier On top of this, you can connect your Ghost site to more than 1,000 external services using the official integration with Zapier .
Zapier sets up automations with Triggers and Actions, which allows you to create and customise a wide range of connected applications.
Example : When someone new subscribes to a newsletter on a Ghost site (Trigger) then the contact information is automatically pushed into MailChimp (Action).Here are the most popular Ghost<>Zapier automation templates:
\nCustom integrations At the heart of Ghost sits a robust JSON API โ designed to create, manage and retrieve content with ease.
It's possible to create custom Ghost integrations with dedicated API keys and webhooks from the Integrations page within Ghost Admin.
Beyond that, the API allows you to build entirely custom publishing apps. You can send content from your favourite desktop editor, build a custom interface for handling editorial workflow or use Ghost as a full headless CMS with a custom front-end.
The Ghost API is thoroughly documented and straightforward to work with for developers of almost any level.
Final step: Themes Alright, on to the last post in our welcome-series! If you're curious about creating your own Ghost theme from scratch, find out how that works .
",
+ "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[[\"image\",{\"src\":\"https://static.ghost.org/v3.0.0/images/integrations-icons.png\",\"cardWidth\":\"full\"}],[\"markdown\",{\"markdown\":\"\\n\"}],[\"image\",{\"src\":\"https://static.ghost.org/v3.0.0/images/integrations-and-webhooks-in-ghost.png\",\"alt\":\"Screenshot of custom integrations with webhooks in Ghost Admin\",\"cardWidth\":\"\"}]],\"markups\":[[\"a\",[\"href\",\"https://ghost.org/integrations/\"]],[\"a\",[\"href\",\"https://zapier.com\"]],[\"strong\"],[\"a\",[\"href\",\"https://docs.ghost.org/api/\"]],[\"a\",[\"href\",\"/themes/\"]]],\"sections\":[[1,\"h2\",[[0,[],0,\"Work with your existing tools\"]]],[1,\"p\",[[0,[],0,\"It's possible to connect your Ghost site to hundreds of the most popular apps and tools using integrations that take no more than a few minutes to setup.\"]]],[1,\"p\",[[0,[],0,\"Whether you need to automate workflows, connect your email list, build a community or embed products from your ecommerce store, our \"],[0,[0],1,\"integrations library\"],[0,[],0,\" has got it all covered with hundreds of tutorials.\"]]],[10,0],[1,\"h2\",[[0,[],0,\"Zapier\"]]],[1,\"p\",[[0,[],0,\"On top of this, you can connect your Ghost site to more than 1,000 external services using the official integration with \"],[0,[1],1,\"Zapier\"],[0,[],0,\".\"]]],[1,\"p\",[[0,[],0,\"Zapier sets up automations with Triggers and Actions, which allows you to create and customise a wide range of connected applications.\"]]],[1,\"blockquote\",[[0,[2],1,\"Example\"],[0,[],0,\": When someone new subscribes to a newsletter on a Ghost site (Trigger) then the contact information is automatically pushed into MailChimp (Action).\"]]],[1,\"p\",[[0,[2],1,\"Here are the most popular Ghost<>Zapier automation templates:\"],[0,[],0,\" \"]]],[10,1],[1,\"h2\",[[0,[],0,\"Custom integrations\"]]],[1,\"p\",[[0,[],0,\"At the heart of Ghost sits a robust JSON API โ designed to create, manage and retrieve content with ease. \"]]],[1,\"p\",[[0,[],0,\"It's possible to create custom Ghost integrations with dedicated API keys and webhooks from the Integrations page within Ghost Admin. \"]]],[10,2],[1,\"p\",[[0,[],0,\"Beyond that, the API allows you to build entirely custom publishing apps. You can send content from your favourite desktop editor, build a custom interface for handling editorial workflow or use Ghost as a full headless CMS with a custom front-end.\"]]],[1,\"p\",[[0,[],0,\"The Ghost API is \"],[0,[3],1,\"thoroughly documented\"],[0,[],0,\" and straightforward to work with for developers of almost any level. \"]]],[1,\"h2\",[[0,[],0,\"Final step: Themes\"]]],[1,\"p\",[[0,[],0,\"Alright, on to the last post in our welcome-series! If you're curious about creating your own Ghost theme from scratch, \"],[0,[4],1,\"find out how that works\"],[0,[],0,\".\"]]]]}",
+ "html": "It's possible to connect your Ghost site to hundreds of the most popular apps and tools using integrations that take no more than a few minutes to setup.
Whether you need to automate workflows, connect your email list, build a community or embed products from your ecommerce store, our integrations library has got it all covered with hundreds of tutorials.
Zapier On top of this, you can connect your Ghost site to more than 1,000 external services using the official integration with Zapier .
Zapier sets up automations with Triggers and Actions, which allows you to create and customise a wide range of connected applications.
Example : When someone new subscribes to a newsletter on a Ghost site (Trigger) then the contact information is automatically pushed into MailChimp (Action).Here are the most popular Ghost<>Zapier automation templates:
\nCustom integrations At the heart of Ghost sits a robust JSON API โ designed to create, manage and retrieve content with ease.
It's possible to create custom Ghost integrations with dedicated API keys and webhooks from the Integrations page within Ghost Admin.
Beyond that, the API allows you to build entirely custom publishing apps. You can send content from your favourite desktop editor, build a custom interface for handling editorial workflow or use Ghost as a full headless CMS with a custom front-end.
The Ghost API is thoroughly documented and straightforward to work with for developers of almost any level.
Final step: Themes Alright, on to the last post in our welcome-series! If you're curious about creating your own Ghost theme from scratch, find out how that works .
",
"comment_id": "605a8e13b6cf646d6629d6c4",
- "plaintext": "Work with your existing tools\nIt's possible to connect your Ghost site to hundreds of the most popular apps\nand tools using integrations that take no more than a few minutes to setup.\n\nWhether you need to automate workflows, connect your email list, build a\ncommunity or embed products from your ecommerce store, our integrations library\n[https://ghost.org/integrations/] has got it all covered with hundreds of\ntutorials.\n\nZapier\nOn top of this, you can connect your Ghost site to more than 1,000 external\nservices using the official integration with Zapier [https://zapier.com].\n\nZapier sets up automations with Triggers and Actions, which allows you to create\nand customise a wide range of connected applications.\n\n> Example: When someone new subscribes to a newsletter on a Ghost site (Trigger)\nthen the contact information is automatically pushed into MailChimp (Action).\nHere are the most popular Ghost<>Zapier automation templates: \n\nCustom integrations\nAt the heart of Ghost sits a robust JSON API โ designed to create, manage and\nretrieve content with ease. \n\nIt's possible to create custom Ghost integrations with dedicated API keys and\nwebhooks from the Integrations page within Ghost Admin. \n\nBeyond that, the API allows you to build entirely custom publishing apps. You\ncan send content from your favourite desktop editor, build a custom interface\nfor handling editorial workflow or use Ghost as a full headless CMS with a\ncustom front-end.\n\nThe Ghost API is thoroughly documented [https://ghost.org/docs/api/] and\nstraightforward to work with for developers of almost any level. \n\nFinal step: Themes\nAlright, on to the last post in our welcome-series! If you're curious about\ncreating your own Ghost theme from scratch, find out how that works [/themes/].",
+ "plaintext": "Work with your existing tools\nIt's possible to connect your Ghost site to hundreds of the most popular apps\nand tools using integrations that take no more than a few minutes to setup.\n\nWhether you need to automate workflows, connect your email list, build a\ncommunity or embed products from your ecommerce store, our integrations library\n[https://ghost.org/integrations/] has got it all covered with hundreds of\ntutorials.\n\nZapier\nOn top of this, you can connect your Ghost site to more than 1,000 external\nservices using the official integration with Zapier [https://zapier.com].\n\nZapier sets up automations with Triggers and Actions, which allows you to create\nand customise a wide range of connected applications.\n\n> Example: When someone new subscribes to a newsletter on a Ghost site (Trigger)\nthen the contact information is automatically pushed into MailChimp (Action).\nHere are the most popular Ghost<>Zapier automation templates: \n\nCustom integrations\nAt the heart of Ghost sits a robust JSON API โ designed to create, manage and\nretrieve content with ease. \n\nIt's possible to create custom Ghost integrations with dedicated API keys and\nwebhooks from the Integrations page within Ghost Admin. \n\nBeyond that, the API allows you to build entirely custom publishing apps. You\ncan send content from your favourite desktop editor, build a custom interface\nfor handling editorial workflow or use Ghost as a full headless CMS with a\ncustom front-end.\n\nThe Ghost API is thoroughly documented [https://docs.ghost.org/api/] and\nstraightforward to work with for developers of almost any level. \n\nFinal step: Themes\nAlright, on to the last post in our welcome-series! If you're curious about\ncreating your own Ghost theme from scratch, find out how that works [/themes/].",
"feature_image": "https://static.ghost.org/v3.0.0/images/app-integrations.png",
"featured": 0,
"type": "post",
@@ -2050,10 +2050,10 @@
"uuid": "b0ba5304-9b70-4bcd-a972-2b6bebabff7d",
"title": "Organising your content",
"slug": "organising-content",
- "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[[\"soft-return\",\"\",{}]],\"cards\":[],\"markups\":[[\"code\"],[\"em\"],[\"strong\"],[\"a\",[\"href\",\"https://ghost.org/docs/api/handlebars-themes/\"]],[\"a\",[\"href\",\"http://yaml.org/spec/1.2/spec.html\",\"rel\",\"noreferrer nofollow noopener\"]],[\"a\",[\"href\",\"https://ghost.org/docs/api/handlebars-themes/routing/\"]],[\"a\",[\"href\",\"/apps-integrations/\"]]],\"sections\":[[1,\"h2\",[[0,[],0,\"Sensible tagging\"]]],[1,\"p\",[[0,[],0,\"You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.\"]]],[1,\"p\",[[0,[],0,\"When you create content for your publication you can assign tags to help differentiate between categories of content. \"]]],[1,\"p\",[[0,[],0,\"For example you may tag some content with News and other content with Podcast, which would create two distinct categories of content listed on \"],[0,[0],1,\"/tag/news/\"],[0,[],0,\" and \"],[0,[0],1,\"/tag/podcast/\"],[0,[],0,\", respectively.\"]]],[1,\"p\",[[0,[],0,\"If you tag a post with both \"],[0,[0],1,\"News\"],[0,[],0,\" \"],[0,[1],1,\"and\"],[0,[],0,\" \"],[0,[0],1,\"Podcast\"],[0,[],0,\" - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.\"]]],[1,\"h3\",[[0,[],0,\"The primary tag\"]]],[1,\"p\",[[0,[],0,\"Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default. \"]]],[1,\"blockquote\",[[0,[1,2],1,\"News\"],[0,[],1,\", Technology, Startup\"]]],[1,\"p\",[[0,[],0,\"So you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.\"]]],[1,\"h3\",[[0,[],0,\"Private tags\"]]],[1,\"p\",[[0,[],0,\"Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.\"]]],[1,\"p\",[[0,[],0,\"For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.\"]]],[1,\"blockquote\",[[0,[1,2],1,\"News\"],[0,[],1,\", #video\"]]],[1,\"p\",[[0,[],0,\"Here, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting. \"]]],[1,\"blockquote\",[[0,[1],0,\"You can find documentation for theme development techniques like this and many more over on Ghost's extensive \"],[0,[3],1,\"theme docs\"],[0,[],1,\".\"]]],[1,\"h2\",[[0,[],0,\"Dynamic routing\"]]],[1,\"p\",[[0,[],0,\"Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates. \"]]],[1,\"p\",[[0,[],0,\"You may not want content tagged with \"],[0,[0],1,\"News\"],[0,[],0,\" to exist on: \"],[0,[0],1,\"example.com/tag/news\"],[0,[],0,\". Instead, you want it to exist on \"],[0,[0],1,\"example.com/news\"],[0,[],0,\" .\"]]],[1,\"p\",[[0,[],0,\"In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.\"]]],[1,\"p\",[[0,[],0,\"There are lots of use cases for dynamic routing with Ghost, here are a few common examples: \"]]],[3,\"ul\",[[[0,[],0,\"Setting a custom home page with its own template\"]],[[0,[],0,\"Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content\"]],[[0,[],0,\"Creating a founders column as a unique view, by filtering content created by specific authors\"]],[[0,[],0,\"Including dates in permalinks for your posts\"]],[[0,[],0,\"Setting posts to have a URL relative to their primary tag like \"],[0,[0],1,\"example.com/europe/story-title/\"],[1,[],0,0]]]],[1,\"blockquote\",[[0,[1],0,\"Dynamic routing can be configured in Ghost using \"],[0,[4],1,\"YAML\"],[0,[],0,\" files. Read our dynamic routing \"],[0,[5],1,\"documentation\"],[0,[],1,\" for further details.\"]]],[1,\"h2\",[[0,[],0,\"Next: Apps & Integrations\"]]],[1,\"p\",[[0,[],0,\"Work with all your favourite apps and tools using our \"],[0,[6],1,\"integrations\"],[0,[],0,\", or create your own custom integrations with webhooks.\"]]],[1,\"p\",[]]]}",
- "html": "Sensible tagging You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.
When you create content for your publication you can assign tags to help differentiate between categories of content.
For example you may tag some content with News and other content with Podcast, which would create two distinct categories of content listed on /tag/news/ and /tag/podcast/, respectively.
If you tag a post with both News and Podcast - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.
The primary tag Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default.
News , Technology, StartupSo you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.
Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.
For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.
News , #videoHere, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting.
You can find documentation for theme development techniques like this and many more over on Ghost's extensive theme docs . Dynamic routing Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates.
You may not want content tagged with News to exist on: example.com/tag/news. Instead, you want it to exist on example.com/news .
In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.
There are lots of use cases for dynamic routing with Ghost, here are a few common examples:
Setting a custom home page with its own template Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content Creating a founders column as a unique view, by filtering content created by specific authors Including dates in permalinks for your posts Setting posts to have a URL relative to their primary tag like example.com/europe/story-title/ Dynamic routing can be configured in Ghost using YAML files. Read our dynamic routing documentation for further details. Next: Apps & Integrations Work with all your favourite apps and tools using our integrations , or create your own custom integrations with webhooks.
",
+ "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[[\"soft-return\",\"\",{}]],\"cards\":[],\"markups\":[[\"code\"],[\"em\"],[\"strong\"],[\"a\",[\"href\",\"https://docs.ghost.org/api/handlebars-themes/\"]],[\"a\",[\"href\",\"http://yaml.org/spec/1.2/spec.html\",\"rel\",\"noreferrer nofollow noopener\"]],[\"a\",[\"href\",\"https://docs.ghost.org/api/handlebars-themes/routing/\"]],[\"a\",[\"href\",\"/apps-integrations/\"]]],\"sections\":[[1,\"h2\",[[0,[],0,\"Sensible tagging\"]]],[1,\"p\",[[0,[],0,\"You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.\"]]],[1,\"p\",[[0,[],0,\"When you create content for your publication you can assign tags to help differentiate between categories of content. \"]]],[1,\"p\",[[0,[],0,\"For example you may tag some content with News and other content with Podcast, which would create two distinct categories of content listed on \"],[0,[0],1,\"/tag/news/\"],[0,[],0,\" and \"],[0,[0],1,\"/tag/podcast/\"],[0,[],0,\", respectively.\"]]],[1,\"p\",[[0,[],0,\"If you tag a post with both \"],[0,[0],1,\"News\"],[0,[],0,\" \"],[0,[1],1,\"and\"],[0,[],0,\" \"],[0,[0],1,\"Podcast\"],[0,[],0,\" - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.\"]]],[1,\"h3\",[[0,[],0,\"The primary tag\"]]],[1,\"p\",[[0,[],0,\"Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default. \"]]],[1,\"blockquote\",[[0,[1,2],1,\"News\"],[0,[],1,\", Technology, Startup\"]]],[1,\"p\",[[0,[],0,\"So you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.\"]]],[1,\"h3\",[[0,[],0,\"Private tags\"]]],[1,\"p\",[[0,[],0,\"Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.\"]]],[1,\"p\",[[0,[],0,\"For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.\"]]],[1,\"blockquote\",[[0,[1,2],1,\"News\"],[0,[],1,\", #video\"]]],[1,\"p\",[[0,[],0,\"Here, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting. \"]]],[1,\"blockquote\",[[0,[1],0,\"You can find documentation for theme development techniques like this and many more over on Ghost's extensive \"],[0,[3],1,\"theme docs\"],[0,[],1,\".\"]]],[1,\"h2\",[[0,[],0,\"Dynamic routing\"]]],[1,\"p\",[[0,[],0,\"Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates. \"]]],[1,\"p\",[[0,[],0,\"You may not want content tagged with \"],[0,[0],1,\"News\"],[0,[],0,\" to exist on: \"],[0,[0],1,\"example.com/tag/news\"],[0,[],0,\". Instead, you want it to exist on \"],[0,[0],1,\"example.com/news\"],[0,[],0,\" .\"]]],[1,\"p\",[[0,[],0,\"In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.\"]]],[1,\"p\",[[0,[],0,\"There are lots of use cases for dynamic routing with Ghost, here are a few common examples: \"]]],[3,\"ul\",[[[0,[],0,\"Setting a custom home page with its own template\"]],[[0,[],0,\"Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content\"]],[[0,[],0,\"Creating a founders column as a unique view, by filtering content created by specific authors\"]],[[0,[],0,\"Including dates in permalinks for your posts\"]],[[0,[],0,\"Setting posts to have a URL relative to their primary tag like \"],[0,[0],1,\"example.com/europe/story-title/\"],[1,[],0,0]]]],[1,\"blockquote\",[[0,[1],0,\"Dynamic routing can be configured in Ghost using \"],[0,[4],1,\"YAML\"],[0,[],0,\" files. Read our dynamic routing \"],[0,[5],1,\"documentation\"],[0,[],1,\" for further details.\"]]],[1,\"h2\",[[0,[],0,\"Next: Apps & Integrations\"]]],[1,\"p\",[[0,[],0,\"Work with all your favourite apps and tools using our \"],[0,[6],1,\"integrations\"],[0,[],0,\", or create your own custom integrations with webhooks.\"]]],[1,\"p\",[]]]}",
+ "html": "Sensible tagging You can think of tags like Gmail labels. By tagging posts with one or more keyword, you can organise articles into buckets of related content.
When you create content for your publication you can assign tags to help differentiate between categories of content.
For example you may tag some content with News and other content with Podcast, which would create two distinct categories of content listed on /tag/news/ and /tag/podcast/, respectively.
If you tag a post with both News and Podcast - then it appears in both sections. Tag archives are like dedicated home-pages for each category of content that you have. They have their own pages, their own RSS feeds, and can support their own cover images and meta data.
The primary tag Inside the Ghost editor, you can drag and drop tags into a specific order. The first tag in the list is always given the most importance, and some themes will only display the primary tag (the first tag in the list) by default.
News , Technology, StartupSo you can add the most important tag which you want to show up in your theme, but also add related tags which are less important.
Sometimes you may want to assign a post a specific tag, but you don't necessarily want that tag appearing in the theme or creating an archive page. In Ghost, hashtags are private and can be used for special styling.
For example, if you sometimes publish posts with video content - you might want your theme to adapt and get rid of the sidebar for these posts, to give more space for an embedded video to fill the screen. In this case, you could use private tags to tell your theme what to do.
News , #videoHere, the theme would assign the post publicly displayed tags of News - but it would also keep a private record of the post being tagged with #video. In your theme, you could then look for private tags conditionally and give them special formatting.
You can find documentation for theme development techniques like this and many more over on Ghost's extensive theme docs . Dynamic routing Dynamic routing gives you the ultimate freedom to build a custom publication to suit your needs. Routes are rules that map URL patterns to your content and templates.
You may not want content tagged with News to exist on: example.com/tag/news. Instead, you want it to exist on example.com/news .
In this case you can use dynamic routes to create customised collections of content on your site. It's also possible to use multiple templates in your theme to render each content type differently.
There are lots of use cases for dynamic routing with Ghost, here are a few common examples:
Setting a custom home page with its own template Having separate content hubs for blog and podcast, that render differently, and have custom RSS feeds to support two types of content Creating a founders column as a unique view, by filtering content created by specific authors Including dates in permalinks for your posts Setting posts to have a URL relative to their primary tag like example.com/europe/story-title/ Dynamic routing can be configured in Ghost using YAML files. Read our dynamic routing documentation for further details. Next: Apps & Integrations Work with all your favourite apps and tools using our integrations , or create your own custom integrations with webhooks.
",
"comment_id": "605a8e13b6cf646d6629d6c6",
- "plaintext": "Sensible tagging\nYou can think of tags like Gmail labels. By tagging posts with one or more\nkeyword, you can organise articles into buckets of related content.\n\nWhen you create content for your publication you can assign tags to help\ndifferentiate between categories of content. \n\nFor example you may tag some content with News and other content with Podcast,\nwhich would create two distinct categories of content listed on /tag/news/ and \n/tag/podcast/, respectively.\n\nIf you tag a post with both News and Podcast - then it appears in both sections.\nTag archives are like dedicated home-pages for each category of content that you\nhave. They have their own pages, their own RSS feeds, and can support their own\ncover images and meta data.\n\nThe primary tag\nInside the Ghost editor, you can drag and drop tags into a specific order. The\nfirst tag in the list is always given the most importance, and some themes will\nonly display the primary tag (the first tag in the list) by default. \n\n> News, Technology, Startup\nSo you can add the most important tag which you want to show up in your theme,\nbut also add related tags which are less important.\n\nPrivate tags\nSometimes you may want to assign a post a specific tag, but you don't\nnecessarily want that tag appearing in the theme or creating an archive page. In\nGhost, hashtags are private and can be used for special styling.\n\nFor example, if you sometimes publish posts with video content - you might want\nyour theme to adapt and get rid of the sidebar for these posts, to give more\nspace for an embedded video to fill the screen. In this case, you could use\nprivate tags to tell your theme what to do.\n\n> News, #video\nHere, the theme would assign the post publicly displayed tags of News - but it\nwould also keep a private record of the post being tagged with #video. In your\ntheme, you could then look for private tags conditionally and give them special\nformatting. \n\n> You can find documentation for theme development techniques like this and many\nmore over on Ghost's extensive theme docs\n[https://ghost.org/docs/api/handlebars-themes/].\nDynamic routing\nDynamic routing gives you the ultimate freedom to build a custom publication to\nsuit your needs. Routes are rules that map URL patterns to your content and\ntemplates. \n\nYou may not want content tagged with News to exist on: example.com/tag/news.\nInstead, you want it to exist on example.com/news .\n\nIn this case you can use dynamic routes to create customised collections of\ncontent on your site. It's also possible to use multiple templates in your theme\nto render each content type differently.\n\nThere are lots of use cases for dynamic routing with Ghost, here are a few\ncommon examples: \n\n * Setting a custom home page with its own template\n * Having separate content hubs for blog and podcast, that render differently,\n and have custom RSS feeds to support two types of content\n * Creating a founders column as a unique view, by filtering content created by\n specific authors\n * Including dates in permalinks for your posts\n * Setting posts to have a URL relative to their primary tag like \n example.com/europe/story-title/\n \n\n> Dynamic routing can be configured in Ghost using YAML\n[http://yaml.org/spec/1.2/spec.html] files. Read our dynamic routing \ndocumentation [https://ghost.org/docs/api/handlebars-themes/routing/] for\nfurther details.\nNext: Apps & Integrations\nWork with all your favourite apps and tools using our integrations\n[/apps-integrations/], or create your own custom integrations with webhooks.",
+ "plaintext": "Sensible tagging\nYou can think of tags like Gmail labels. By tagging posts with one or more\nkeyword, you can organise articles into buckets of related content.\n\nWhen you create content for your publication you can assign tags to help\ndifferentiate between categories of content. \n\nFor example you may tag some content with News and other content with Podcast,\nwhich would create two distinct categories of content listed on /tag/news/ and \n/tag/podcast/, respectively.\n\nIf you tag a post with both News and Podcast - then it appears in both sections.\nTag archives are like dedicated home-pages for each category of content that you\nhave. They have their own pages, their own RSS feeds, and can support their own\ncover images and meta data.\n\nThe primary tag\nInside the Ghost editor, you can drag and drop tags into a specific order. The\nfirst tag in the list is always given the most importance, and some themes will\nonly display the primary tag (the first tag in the list) by default. \n\n> News, Technology, Startup\nSo you can add the most important tag which you want to show up in your theme,\nbut also add related tags which are less important.\n\nPrivate tags\nSometimes you may want to assign a post a specific tag, but you don't\nnecessarily want that tag appearing in the theme or creating an archive page. In\nGhost, hashtags are private and can be used for special styling.\n\nFor example, if you sometimes publish posts with video content - you might want\nyour theme to adapt and get rid of the sidebar for these posts, to give more\nspace for an embedded video to fill the screen. In this case, you could use\nprivate tags to tell your theme what to do.\n\n> News, #video\nHere, the theme would assign the post publicly displayed tags of News - but it\nwould also keep a private record of the post being tagged with #video. In your\ntheme, you could then look for private tags conditionally and give them special\nformatting. \n\n> You can find documentation for theme development techniques like this and many\nmore over on Ghost's extensive theme docs\n[https://docs.ghost.org/api/handlebars-themes/].\nDynamic routing\nDynamic routing gives you the ultimate freedom to build a custom publication to\nsuit your needs. Routes are rules that map URL patterns to your content and\ntemplates. \n\nYou may not want content tagged with News to exist on: example.com/tag/news.\nInstead, you want it to exist on example.com/news .\n\nIn this case you can use dynamic routes to create customised collections of\ncontent on your site. It's also possible to use multiple templates in your theme\nto render each content type differently.\n\nThere are lots of use cases for dynamic routing with Ghost, here are a few\ncommon examples: \n\n * Setting a custom home page with its own template\n * Having separate content hubs for blog and podcast, that render differently,\n and have custom RSS feeds to support two types of content\n * Creating a founders column as a unique view, by filtering content created by\n specific authors\n * Including dates in permalinks for your posts\n * Setting posts to have a URL relative to their primary tag like \n example.com/europe/story-title/\n \n\n> Dynamic routing can be configured in Ghost using YAML\n[http://yaml.org/spec/1.2/spec.html] files. Read our dynamic routing \ndocumentation [https://docs.ghost.org/api/handlebars-themes/routing/] for\nfurther details.\nNext: Apps & Integrations\nWork with all your favourite apps and tools using our integrations\n[/apps-integrations/], or create your own custom integrations with webhooks.",
"feature_image": "https://static.ghost.org/v3.0.0/images/organising-your-content.png",
"featured": 0,
"type": "post",
@@ -2486,7 +2486,7 @@
{
"id": "605a8e14b6cf646d6629d79c",
"key": "navigation",
- "value": "[{\"label\":\"Home\", \"url\":\"/\"},{\"label\":\"Tag\", \"url\":\"/tag/getting-started/\"}, {\"label\":\"Author\", \"url\":\"/author/ghost/\"},{\"label\":\"Help\", \"url\":\"https://ghost.org/docs/\"}]",
+ "value": "[{\"label\":\"Home\", \"url\":\"/\"},{\"label\":\"Tag\", \"url\":\"/tag/getting-started/\"}, {\"label\":\"Author\", \"url\":\"/author/ghost/\"},{\"label\":\"Help\", \"url\":\"https://docs.ghost.org/\"}]",
"type": "blog",
"created_at": "2021-03-24T00:55:48.000Z",
"updated_at": "2021-03-24T00:55:48.000Z"
diff --git a/ghost/core/test/utils/fixtures/export/v4_export.json b/ghost/core/test/utils/fixtures/export/v4_export.json
index 9304367c2f4..0e4c914a78b 100644
--- a/ghost/core/test/utils/fixtures/export/v4_export.json
+++ b/ghost/core/test/utils/fixtures/export/v4_export.json
@@ -3169,10 +3169,10 @@
"uuid": "6f28cfd8-7879-4608-8062-d5533eef63ac",
"title": "Customizing your brand and design settings",
"slug": "design",
- "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[[\"image\",{\"src\":\"https://static.ghost.org/v4.0.0/images/brandsettings.png\",\"width\":3456,\"height\":2338,\"cardWidth\":\"wide\",\"caption\":\"Ghost Admin โ Settings โ Branding\"}],[\"image\",{\"src\":\"https://static.ghost.org/v4.0.0/images/themesettings.png\",\"width\":3208,\"height\":1618,\"cardWidth\":\"wide\",\"caption\":\"Ghost Admin โ Settings โ Theme\"}],[\"code\",{\"code\":\"{{#post}}\\n\\n\\n {{title}} \\n \\n {{#if feature_image}}\\n \\t \\n {{/if}}\\n \\n {{content}}\\n\\n \\n{{/post}}\",\"language\":\"handlebars\",\"caption\":\"A snippet from a post template\"}]],\"markups\":[[\"a\",[\"href\",\"__GHOST_URL__/welcome/\"]],[\"strong\"],[\"em\"],[\"a\",[\"href\",\"https://ghost.org/themes/\"]],[\"a\",[\"href\",\"https://github.com/tryghost/casper/\"]],[\"a\",[\"href\",\"https://ghost.org/docs/themes/\"]]],\"sections\":[[1,\"p\",[[0,[],0,\"As discussed in the \"],[0,[0],1,\"introduction\"],[0,[],0,\" post, one of the best things about Ghost is just how much you can customize to turn your site into something unique. Everything about your layout and design can be changed, so you're not stuck with yet another clone of a social network profile.\"]]],[1,\"p\",[[0,[],0,\"How far you want to go with customization is completely up to you, there's no right or wrong approach! The majority of people use one of Ghost's built-in themes to get started, and then progress to something more bespoke later on as their site grows. \"]]],[1,\"p\",[[0,[],0,\"The best way to get started is with Ghost's branding settings, where you can set up colors, images and logos to fit with your brand.\"]]],[10,0],[1,\"p\",[[0,[],0,\"Any Ghost theme that's up to date and compatible with Ghost 4.0 and higher will reflect your branding settings in the preview window, so you can see what your site will look like as you experiment with different options.\"]]],[1,\"p\",[[0,[],0,\"When selecting an accent color, try to choose something which will contrast well with white text. Many themes will use your accent color as the background for buttons, headers and navigational elements. Vibrant colors with a darker hue tend to work best, as a general rule.\"]]],[1,\"h2\",[[0,[],0,\"Installing Ghost themes\"]]],[1,\"p\",[[0,[],0,\"By default, new sites are created with Ghost's friendly publication theme, called Casper. Everything in Casper is optimized to work for the most common types of blog, newsletter and publication that people create with Ghost โ so it's a perfect place to start.\"]]],[1,\"p\",[[0,[],0,\"However, there are hundreds of different themes available to install, so you can pick out a look and feel that suits you best.\"]]],[10,1],[1,\"p\",[[0,[],0,\"Inside Ghost's theme settings you'll find 4 more official themes that can be directly installed and activated. Each theme is suited to slightly different use-cases.\"]]],[3,\"ul\",[[[0,[1],1,\"Casper\"],[0,[],0,\" \"],[0,[2],1,\"(default)\"],[0,[],0,\" โ Made for all sorts of blogs and newsletters\"]],[[0,[1],1,\"Edition\"],[0,[],0,\" โ A beautiful minimal template for newsletter authors\"]],[[0,[1],1,\"Alto\"],[0,[],0,\" โ A slick news/magazine style design for creators\"]],[[0,[1],1,\"London\"],[0,[],0,\" โ A light photography theme with a bold grid\"]],[[0,[1],1,\"Ease\"],[0,[],0,\" โ A library theme for organizing large content archives\"]]]],[1,\"p\",[[0,[],0,\"And if none of those feel quite right, head on over to the \"],[0,[3],1,\"Ghost Marketplace\"],[0,[],0,\", where you'll find a huge variety of both free and premium themes.\"]]],[1,\"h2\",[[0,[],0,\"Building something custom\"]]],[1,\"p\",[[0,[],0,\"Finally, if you want something completely bespoke for your site, you can always build a custom theme from scratch and upload it to your site.\"]]],[1,\"p\",[[0,[],0,\"Ghost's theming template files are very easy to work with, and can be picked up in the space of a few hours by anyone who has just a little bit of knowledge of HTML and CSS. Templates from other platforms can also be ported to Ghost with relatively little effort.\"]]],[1,\"p\",[[0,[],0,\"If you want to take a quick look at the theme syntax to see what it's like, you can \"],[0,[4],1,\"browse through the files of the default Casper theme\"],[0,[],0,\". We've added tons of inline code comments to make it easy to learn, and the structure is very readable.\"]]],[10,2],[1,\"p\",[[0,[],0,\"See? Not that scary! But still completely optional. \"]]],[1,\"p\",[[0,[],0,\"If you're interested in creating your own Ghost theme, check out our extensive \"],[0,[5],1,\"theme documentation\"],[0,[],0,\" for a full guide to all the different template variables and helpers which are available.\"]]]],\"ghostVersion\":\"4.0\"}",
- "html": "As discussed in the introduction post, one of the best things about Ghost is just how much you can customize to turn your site into something unique. Everything about your layout and design can be changed, so you're not stuck with yet another clone of a social network profile.
How far you want to go with customization is completely up to you, there's no right or wrong approach! The majority of people use one of Ghost's built-in themes to get started, and then progress to something more bespoke later on as their site grows.
The best way to get started is with Ghost's branding settings, where you can set up colors, images and logos to fit with your brand.
Ghost Admin โ Settings โ Branding Any Ghost theme that's up to date and compatible with Ghost 4.0 and higher will reflect your branding settings in the preview window, so you can see what your site will look like as you experiment with different options.
When selecting an accent color, try to choose something which will contrast well with white text. Many themes will use your accent color as the background for buttons, headers and navigational elements. Vibrant colors with a darker hue tend to work best, as a general rule.
Installing Ghost themes By default, new sites are created with Ghost's friendly publication theme, called Casper. Everything in Casper is optimized to work for the most common types of blog, newsletter and publication that people create with Ghost โ so it's a perfect place to start.
However, there are hundreds of different themes available to install, so you can pick out a look and feel that suits you best.
Ghost Admin โ Settings โ Theme Inside Ghost's theme settings you'll find 4 more official themes that can be directly installed and activated. Each theme is suited to slightly different use-cases.
Casper (default) โ Made for all sorts of blogs and newslettersEdition โ A beautiful minimal template for newsletter authorsAlto โ A slick news/magazine style design for creatorsLondon โ A light photography theme with a bold gridEase โ A library theme for organizing large content archivesAnd if none of those feel quite right, head on over to the Ghost Marketplace , where you'll find a huge variety of both free and premium themes.
Building something custom Finally, if you want something completely bespoke for your site, you can always build a custom theme from scratch and upload it to your site.
Ghost's theming template files are very easy to work with, and can be picked up in the space of a few hours by anyone who has just a little bit of knowledge of HTML and CSS. Templates from other platforms can also be ported to Ghost with relatively little effort.
If you want to take a quick look at the theme syntax to see what it's like, you can browse through the files of the default Casper theme . We've added tons of inline code comments to make it easy to learn, and the structure is very readable.
{{#post}}\n<article class=\"article {{post_class}}\">\n\n <h1>{{title}}</h1>\n \n {{#if feature_image}}\n \t<img src=\"{{feature_image}}\" alt=\"Feature image\" />\n {{/if}}\n \n {{content}}\n\n</article>\n{{/post}}A snippet from a post template See? Not that scary! But still completely optional.
If you're interested in creating your own Ghost theme, check out our extensive theme documentation for a full guide to all the different template variables and helpers which are available.
",
+ "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[[\"image\",{\"src\":\"https://static.ghost.org/v4.0.0/images/brandsettings.png\",\"width\":3456,\"height\":2338,\"cardWidth\":\"wide\",\"caption\":\"Ghost Admin โ Settings โ Branding\"}],[\"image\",{\"src\":\"https://static.ghost.org/v4.0.0/images/themesettings.png\",\"width\":3208,\"height\":1618,\"cardWidth\":\"wide\",\"caption\":\"Ghost Admin โ Settings โ Theme\"}],[\"code\",{\"code\":\"{{#post}}\\n\\n\\n {{title}} \\n \\n {{#if feature_image}}\\n \\t \\n {{/if}}\\n \\n {{content}}\\n\\n \\n{{/post}}\",\"language\":\"handlebars\",\"caption\":\"A snippet from a post template\"}]],\"markups\":[[\"a\",[\"href\",\"__GHOST_URL__/welcome/\"]],[\"strong\"],[\"em\"],[\"a\",[\"href\",\"https://ghost.org/themes/\"]],[\"a\",[\"href\",\"https://github.com/tryghost/casper/\"]],[\"a\",[\"href\",\"https://docs.ghost.org/themes/\"]]],\"sections\":[[1,\"p\",[[0,[],0,\"As discussed in the \"],[0,[0],1,\"introduction\"],[0,[],0,\" post, one of the best things about Ghost is just how much you can customize to turn your site into something unique. Everything about your layout and design can be changed, so you're not stuck with yet another clone of a social network profile.\"]]],[1,\"p\",[[0,[],0,\"How far you want to go with customization is completely up to you, there's no right or wrong approach! The majority of people use one of Ghost's built-in themes to get started, and then progress to something more bespoke later on as their site grows. \"]]],[1,\"p\",[[0,[],0,\"The best way to get started is with Ghost's branding settings, where you can set up colors, images and logos to fit with your brand.\"]]],[10,0],[1,\"p\",[[0,[],0,\"Any Ghost theme that's up to date and compatible with Ghost 4.0 and higher will reflect your branding settings in the preview window, so you can see what your site will look like as you experiment with different options.\"]]],[1,\"p\",[[0,[],0,\"When selecting an accent color, try to choose something which will contrast well with white text. Many themes will use your accent color as the background for buttons, headers and navigational elements. Vibrant colors with a darker hue tend to work best, as a general rule.\"]]],[1,\"h2\",[[0,[],0,\"Installing Ghost themes\"]]],[1,\"p\",[[0,[],0,\"By default, new sites are created with Ghost's friendly publication theme, called Casper. Everything in Casper is optimized to work for the most common types of blog, newsletter and publication that people create with Ghost โ so it's a perfect place to start.\"]]],[1,\"p\",[[0,[],0,\"However, there are hundreds of different themes available to install, so you can pick out a look and feel that suits you best.\"]]],[10,1],[1,\"p\",[[0,[],0,\"Inside Ghost's theme settings you'll find 4 more official themes that can be directly installed and activated. Each theme is suited to slightly different use-cases.\"]]],[3,\"ul\",[[[0,[1],1,\"Casper\"],[0,[],0,\" \"],[0,[2],1,\"(default)\"],[0,[],0,\" โ Made for all sorts of blogs and newsletters\"]],[[0,[1],1,\"Edition\"],[0,[],0,\" โ A beautiful minimal template for newsletter authors\"]],[[0,[1],1,\"Alto\"],[0,[],0,\" โ A slick news/magazine style design for creators\"]],[[0,[1],1,\"London\"],[0,[],0,\" โ A light photography theme with a bold grid\"]],[[0,[1],1,\"Ease\"],[0,[],0,\" โ A library theme for organizing large content archives\"]]]],[1,\"p\",[[0,[],0,\"And if none of those feel quite right, head on over to the \"],[0,[3],1,\"Ghost Marketplace\"],[0,[],0,\", where you'll find a huge variety of both free and premium themes.\"]]],[1,\"h2\",[[0,[],0,\"Building something custom\"]]],[1,\"p\",[[0,[],0,\"Finally, if you want something completely bespoke for your site, you can always build a custom theme from scratch and upload it to your site.\"]]],[1,\"p\",[[0,[],0,\"Ghost's theming template files are very easy to work with, and can be picked up in the space of a few hours by anyone who has just a little bit of knowledge of HTML and CSS. Templates from other platforms can also be ported to Ghost with relatively little effort.\"]]],[1,\"p\",[[0,[],0,\"If you want to take a quick look at the theme syntax to see what it's like, you can \"],[0,[4],1,\"browse through the files of the default Casper theme\"],[0,[],0,\". We've added tons of inline code comments to make it easy to learn, and the structure is very readable.\"]]],[10,2],[1,\"p\",[[0,[],0,\"See? Not that scary! But still completely optional. \"]]],[1,\"p\",[[0,[],0,\"If you're interested in creating your own Ghost theme, check out our extensive \"],[0,[5],1,\"theme documentation\"],[0,[],0,\" for a full guide to all the different template variables and helpers which are available.\"]]]],\"ghostVersion\":\"4.0\"}",
+ "html": "As discussed in the introduction post, one of the best things about Ghost is just how much you can customize to turn your site into something unique. Everything about your layout and design can be changed, so you're not stuck with yet another clone of a social network profile.
How far you want to go with customization is completely up to you, there's no right or wrong approach! The majority of people use one of Ghost's built-in themes to get started, and then progress to something more bespoke later on as their site grows.
The best way to get started is with Ghost's branding settings, where you can set up colors, images and logos to fit with your brand.
Ghost Admin โ Settings โ Branding Any Ghost theme that's up to date and compatible with Ghost 4.0 and higher will reflect your branding settings in the preview window, so you can see what your site will look like as you experiment with different options.
When selecting an accent color, try to choose something which will contrast well with white text. Many themes will use your accent color as the background for buttons, headers and navigational elements. Vibrant colors with a darker hue tend to work best, as a general rule.
Installing Ghost themes By default, new sites are created with Ghost's friendly publication theme, called Casper. Everything in Casper is optimized to work for the most common types of blog, newsletter and publication that people create with Ghost โ so it's a perfect place to start.
However, there are hundreds of different themes available to install, so you can pick out a look and feel that suits you best.
Ghost Admin โ Settings โ Theme Inside Ghost's theme settings you'll find 4 more official themes that can be directly installed and activated. Each theme is suited to slightly different use-cases.
Casper (default) โ Made for all sorts of blogs and newslettersEdition โ A beautiful minimal template for newsletter authorsAlto โ A slick news/magazine style design for creatorsLondon โ A light photography theme with a bold gridEase โ A library theme for organizing large content archivesAnd if none of those feel quite right, head on over to the Ghost Marketplace , where you'll find a huge variety of both free and premium themes.
Building something custom Finally, if you want something completely bespoke for your site, you can always build a custom theme from scratch and upload it to your site.
Ghost's theming template files are very easy to work with, and can be picked up in the space of a few hours by anyone who has just a little bit of knowledge of HTML and CSS. Templates from other platforms can also be ported to Ghost with relatively little effort.
If you want to take a quick look at the theme syntax to see what it's like, you can browse through the files of the default Casper theme . We've added tons of inline code comments to make it easy to learn, and the structure is very readable.
{{#post}}\n<article class=\"article {{post_class}}\">\n\n <h1>{{title}}</h1>\n \n {{#if feature_image}}\n \t<img src=\"{{feature_image}}\" alt=\"Feature image\" />\n {{/if}}\n \n {{content}}\n\n</article>\n{{/post}}A snippet from a post template See? Not that scary! But still completely optional.
If you're interested in creating your own Ghost theme, check out our extensive theme documentation for a full guide to all the different template variables and helpers which are available.
",
"comment_id": "605ac141a2d5a6aa9e101ec7",
- "plaintext": "As discussed in the introduction [__GHOST_URL__/welcome/] post, one of the best\nthings about Ghost is just how much you can customize to turn your site into\nsomething unique. Everything about your layout and design can be changed, so\nyou're not stuck with yet another clone of a social network profile.\n\nHow far you want to go with customization is completely up to you, there's no\nright or wrong approach! The majority of people use one of Ghost's built-in\nthemes to get started, and then progress to something more bespoke later on as\ntheir site grows. \n\nThe best way to get started is with Ghost's branding settings, where you can set\nup colors, images and logos to fit with your brand.\n\nGhost Admin โ Settings โ BrandingAny Ghost theme that's up to date and\ncompatible with Ghost 4.0 and higher will reflect your branding settings in the\npreview window, so you can see what your site will look like as you experiment\nwith different options.\n\nWhen selecting an accent color, try to choose something which will contrast well\nwith white text. Many themes will use your accent color as the background for\nbuttons, headers and navigational elements. Vibrant colors with a darker hue\ntend to work best, as a general rule.\n\nInstalling Ghost themes\nBy default, new sites are created with Ghost's friendly publication theme,\ncalled Casper. Everything in Casper is optimized to work for the most common\ntypes of blog, newsletter and publication that people create with Ghost โ so\nit's a perfect place to start.\n\nHowever, there are hundreds of different themes available to install, so you can\npick out a look and feel that suits you best.\n\nGhost Admin โ Settings โ ThemeInside Ghost's theme settings you'll find 4 more\nofficial themes that can be directly installed and activated. Each theme is\nsuited to slightly different use-cases.\n\n * Casper (default) โ Made for all sorts of blogs and newsletters\n * Edition โ A beautiful minimal template for newsletter authors\n * Alto โ A slick news/magazine style design for creators\n * London โ A light photography theme with a bold grid\n * Ease โ A library theme for organizing large content archives\n\nAnd if none of those feel quite right, head on over to the Ghost Marketplace\n[https://ghost.org/themes/], where you'll find a huge variety of both free and\npremium themes.\n\nBuilding something custom\nFinally, if you want something completely bespoke for your site, you can always\nbuild a custom theme from scratch and upload it to your site.\n\nGhost's theming template files are very easy to work with, and can be picked up\nin the space of a few hours by anyone who has just a little bit of knowledge of\nHTML and CSS. Templates from other platforms can also be ported to Ghost with\nrelatively little effort.\n\nIf you want to take a quick look at the theme syntax to see what it's like, you\ncan browse through the files of the default Casper theme\n[https://github.com/tryghost/casper/]. We've added tons of inline code comments\nto make it easy to learn, and the structure is very readable.\n\n{{#post}}\n\n\n {{title}} \n \n {{#if feature_image}}\n \t \n {{/if}}\n \n {{content}}\n\n \n{{/post}}\n\nA snippet from a post templateSee? Not that scary! But still completely\noptional. \n\nIf you're interested in creating your own Ghost theme, check out our extensive \ntheme documentation [https://ghost.org/docs/themes/] for a full guide to all the\ndifferent template variables and helpers which are available.",
+ "plaintext": "As discussed in the introduction [__GHOST_URL__/welcome/] post, one of the best\nthings about Ghost is just how much you can customize to turn your site into\nsomething unique. Everything about your layout and design can be changed, so\nyou're not stuck with yet another clone of a social network profile.\n\nHow far you want to go with customization is completely up to you, there's no\nright or wrong approach! The majority of people use one of Ghost's built-in\nthemes to get started, and then progress to something more bespoke later on as\ntheir site grows. \n\nThe best way to get started is with Ghost's branding settings, where you can set\nup colors, images and logos to fit with your brand.\n\nGhost Admin โ Settings โ BrandingAny Ghost theme that's up to date and\ncompatible with Ghost 4.0 and higher will reflect your branding settings in the\npreview window, so you can see what your site will look like as you experiment\nwith different options.\n\nWhen selecting an accent color, try to choose something which will contrast well\nwith white text. Many themes will use your accent color as the background for\nbuttons, headers and navigational elements. Vibrant colors with a darker hue\ntend to work best, as a general rule.\n\nInstalling Ghost themes\nBy default, new sites are created with Ghost's friendly publication theme,\ncalled Casper. Everything in Casper is optimized to work for the most common\ntypes of blog, newsletter and publication that people create with Ghost โ so\nit's a perfect place to start.\n\nHowever, there are hundreds of different themes available to install, so you can\npick out a look and feel that suits you best.\n\nGhost Admin โ Settings โ ThemeInside Ghost's theme settings you'll find 4 more\nofficial themes that can be directly installed and activated. Each theme is\nsuited to slightly different use-cases.\n\n * Casper (default) โ Made for all sorts of blogs and newsletters\n * Edition โ A beautiful minimal template for newsletter authors\n * Alto โ A slick news/magazine style design for creators\n * London โ A light photography theme with a bold grid\n * Ease โ A library theme for organizing large content archives\n\nAnd if none of those feel quite right, head on over to the Ghost Marketplace\n[https://ghost.org/themes/], where you'll find a huge variety of both free and\npremium themes.\n\nBuilding something custom\nFinally, if you want something completely bespoke for your site, you can always\nbuild a custom theme from scratch and upload it to your site.\n\nGhost's theming template files are very easy to work with, and can be picked up\nin the space of a few hours by anyone who has just a little bit of knowledge of\nHTML and CSS. Templates from other platforms can also be ported to Ghost with\nrelatively little effort.\n\nIf you want to take a quick look at the theme syntax to see what it's like, you\ncan browse through the files of the default Casper theme\n[https://github.com/tryghost/casper/]. We've added tons of inline code comments\nto make it easy to learn, and the structure is very readable.\n\n{{#post}}\n\n\n {{title}} \n \n {{#if feature_image}}\n \t \n {{/if}}\n \n {{content}}\n\n \n{{/post}}\n\nA snippet from a post templateSee? Not that scary! But still completely\noptional. \n\nIf you're interested in creating your own Ghost theme, check out our extensive \ntheme documentation [https://docs.ghost.org/themes/] for a full guide to all the\ndifferent template variables and helpers which are available.",
"feature_image": "https://static.ghost.org/v4.0.0/images/publishing-options.png",
"featured": 0,
"type": "post",
diff --git a/ghost/core/test/utils/fixtures/export/valid.json b/ghost/core/test/utils/fixtures/export/valid.json
index a4c6bcb9c5d..9c3fd14e7d4 100644
--- a/ghost/core/test/utils/fixtures/export/valid.json
+++ b/ghost/core/test/utils/fixtures/export/valid.json
@@ -1035,9 +1035,9 @@
"uuid": "8c414ae2-dce6-4b0f-8ee6-5c403fa2ae86",
"title": "Setting up your own Ghost theme",
"slug": "themes",
- "mobiledoc": "{\"version\":\"0.3.1\",\"markups\":[],\"atoms\":[],\"cards\":[[\"markdown\",{\"markdown\":\"Creating a totally custom design for your publication\\n\\nGhost comes with a beautiful default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes. However, Ghost can also be completely themed to suit your needs. Rather than just giving you a few basic settings which act as a poor proxy for code, we just let you write code.\\n\\nThere are a huge range of both free and premium pre-built themes which you can get from the [Ghost Theme Marketplace](https://ghost.org/marketplace/), or you can simply create your own from scratch.\\n\\n[](https://ghost.org/marketplace/)\\n\\n> Anyone can write a completely custom Ghost theme, with just some solid knowledge of HTML and CSS\\n\\nGhost themes are written with a templating language called handlebars, which has a bunch of dynamic helpers to insert your data into template files. Like `{{author.name}}`, for example, outputs the name of the current author.\\n\\nThe best way to learn how to write your own Ghost theme is to have a look at [the source code for Casper](https://github.com/TryGhost/Casper), which is heavily commented and should give you a sense of how everything fits together.\\n\\n- `default.hbs` is the main template file, all contexts will load inside this file unless specifically told to use a different template.\\n- `post.hbs` is the file used in the context of viewing a post.\\n- `index.hbs` is the file used in the context of viewing the home page.\\n- and so on\\n\\nWe've got [full and extensive theme documentation](https://ghost.org/docs/themes/) which outlines every template file, context and helper that you can use.\\n\\nIf you want to chat with other people making Ghost themes to get any advice or help, there's also a **themes** section on our [public Ghost forum](https://forum.ghost.org/c/themes).\"}]],\"sections\":[[10,0]]}",
- "html": "Creating a totally custom design for your publication
\nGhost comes with a beautiful default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes. However, Ghost can also be completely themed to suit your needs. Rather than just giving you a few basic settings which act as a poor proxy for code, we just let you write code.
\nThere are a huge range of both free and premium pre-built themes which you can get from the Ghost Theme Marketplace , or you can simply create your own from scratch.
\n
\n\nAnyone can write a completely custom Ghost theme, with just some solid knowledge of HTML and CSS
\n \nGhost themes are written with a templating language called handlebars, which has a bunch of dynamic helpers to insert your data into template files. Like {{author.name}}, for example, outputs the name of the current author.
\nThe best way to learn how to write your own Ghost theme is to have a look at the source code for Casper , which is heavily commented and should give you a sense of how everything fits together.
\n\ndefault.hbs is the main template file, all contexts will load inside this file unless specifically told to use a different template. \npost.hbs is the file used in the context of viewing a post. \nindex.hbs is the file used in the context of viewing the home page. \nand so on \n \nWe've got full and extensive theme documentation which outlines every template file, context and helper that you can use.
\nIf you want to chat with other people making Ghost themes to get any advice or help, there's also a themes section on our public Ghost forum .
",
- "plaintext": "Creating a totally custom design for your publication\n\nGhost comes with a beautiful default theme called Casper, which is designed to\nbe a clean, readable publication layout and can be easily adapted for most\npurposes. However, Ghost can also be completely themed to suit your needs.\nRather than just giving you a few basic settings which act as a poor proxy for\ncode, we just let you write code.\n\nThere are a huge range of both free and premium pre-built themes which you can\nget from the Ghost Theme Marketplace [https://ghost.org/marketplace/], or you can\nsimply create your own from scratch.\n\n [https://ghost.org/marketplace/]\n\nAnyone can write a completely custom Ghost theme, with just some solid knowledge\nof HTML and CSS\n\nGhost themes are written with a templating language called handlebars, which has\na bunch of dynamic helpers to insert your data into template files. Like \n{{author.name}}, for example, outputs the name of the current author.\n\nThe best way to learn how to write your own Ghost theme is to have a look at \nthe\nsource code for Casper [https://github.com/TryGhost/Casper], which is heavily\ncommented and should give you a sense of how everything fits together.\n\n * default.hbs is the main template file, all contexts will load inside this\n file unless specifically told to use a different template.\n * post.hbs is the file used in the context of viewing a post.\n * index.hbs is the file used in the context of viewing the home page.\n * and so on\n\nWe've got full and extensive theme documentation\n[https://ghost.org/docs/themes/] which outlines every template file,\ncontext and helper that you can use.\n\nIf you want to chat with other people making Ghost themes to get any advice or\nhelp, there's also a themes category on our public Ghost forum\n[https://forum.ghost.org/c/themes].",
+ "mobiledoc": "{\"version\":\"0.3.1\",\"markups\":[],\"atoms\":[],\"cards\":[[\"markdown\",{\"markdown\":\"Creating a totally custom design for your publication\\n\\nGhost comes with a beautiful default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes. However, Ghost can also be completely themed to suit your needs. Rather than just giving you a few basic settings which act as a poor proxy for code, we just let you write code.\\n\\nThere are a huge range of both free and premium pre-built themes which you can get from the [Ghost Theme Marketplace](https://ghost.org/marketplace/), or you can simply create your own from scratch.\\n\\n[](https://ghost.org/marketplace/)\\n\\n> Anyone can write a completely custom Ghost theme, with just some solid knowledge of HTML and CSS\\n\\nGhost themes are written with a templating language called handlebars, which has a bunch of dynamic helpers to insert your data into template files. Like `{{author.name}}`, for example, outputs the name of the current author.\\n\\nThe best way to learn how to write your own Ghost theme is to have a look at [the source code for Casper](https://github.com/TryGhost/Casper), which is heavily commented and should give you a sense of how everything fits together.\\n\\n- `default.hbs` is the main template file, all contexts will load inside this file unless specifically told to use a different template.\\n- `post.hbs` is the file used in the context of viewing a post.\\n- `index.hbs` is the file used in the context of viewing the home page.\\n- and so on\\n\\nWe've got [full and extensive theme documentation](https://docs.ghost.org/themes/) which outlines every template file, context and helper that you can use.\\n\\nIf you want to chat with other people making Ghost themes to get any advice or help, there's also a **themes** section on our [public Ghost forum](https://forum.ghost.org/c/themes).\"}]],\"sections\":[[10,0]]}",
+ "html": "Creating a totally custom design for your publication
\nGhost comes with a beautiful default theme called Casper, which is designed to be a clean, readable publication layout and can be easily adapted for most purposes. However, Ghost can also be completely themed to suit your needs. Rather than just giving you a few basic settings which act as a poor proxy for code, we just let you write code.
\nThere are a huge range of both free and premium pre-built themes which you can get from the Ghost Theme Marketplace , or you can simply create your own from scratch.
\n
\n\nAnyone can write a completely custom Ghost theme, with just some solid knowledge of HTML and CSS
\n \nGhost themes are written with a templating language called handlebars, which has a bunch of dynamic helpers to insert your data into template files. Like {{author.name}}, for example, outputs the name of the current author.
\nThe best way to learn how to write your own Ghost theme is to have a look at the source code for Casper , which is heavily commented and should give you a sense of how everything fits together.
\n\ndefault.hbs is the main template file, all contexts will load inside this file unless specifically told to use a different template. \npost.hbs is the file used in the context of viewing a post. \nindex.hbs is the file used in the context of viewing the home page. \nand so on \n \nWe've got full and extensive theme documentation which outlines every template file, context and helper that you can use.
\nIf you want to chat with other people making Ghost themes to get any advice or help, there's also a themes section on our public Ghost forum .
",
+ "plaintext": "Creating a totally custom design for your publication\n\nGhost comes with a beautiful default theme called Casper, which is designed to\nbe a clean, readable publication layout and can be easily adapted for most\npurposes. However, Ghost can also be completely themed to suit your needs.\nRather than just giving you a few basic settings which act as a poor proxy for\ncode, we just let you write code.\n\nThere are a huge range of both free and premium pre-built themes which you can\nget from the Ghost Theme Marketplace [https://ghost.org/marketplace/], or you can\nsimply create your own from scratch.\n\n [https://ghost.org/marketplace/]\n\nAnyone can write a completely custom Ghost theme, with just some solid knowledge\nof HTML and CSS\n\nGhost themes are written with a templating language called handlebars, which has\na bunch of dynamic helpers to insert your data into template files. Like \n{{author.name}}, for example, outputs the name of the current author.\n\nThe best way to learn how to write your own Ghost theme is to have a look at \nthe\nsource code for Casper [https://github.com/TryGhost/Casper], which is heavily\ncommented and should give you a sense of how everything fits together.\n\n * default.hbs is the main template file, all contexts will load inside this\n file unless specifically told to use a different template.\n * post.hbs is the file used in the context of viewing a post.\n * index.hbs is the file used in the context of viewing the home page.\n * and so on\n\nWe've got full and extensive theme documentation\n[https://docs.ghost.org/themes/] which outlines every template file,\ncontext and helper that you can use.\n\nIf you want to chat with other people making Ghost themes to get any advice or\nhelp, there's also a themes category on our public Ghost forum\n[https://forum.ghost.org/c/themes].",
"feature_image": "https://static.ghost.org/v1.0.0/images/design.jpg",
"featured": 0,
"page": 0,
diff --git a/ghost/core/test/utils/fixtures/fixtures.json b/ghost/core/test/utils/fixtures/fixtures.json
index 8fff6a689c1..5f1865484ee 100644
--- a/ghost/core/test/utils/fixtures/fixtures.json
+++ b/ghost/core/test/utils/fixtures/fixtures.json
@@ -963,7 +963,7 @@
"id": "6194d3ce51e2700162531a76",
"title": "Customizing your brand and design settings",
"slug": "design",
- "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[[\"image\",{\"src\":\"https://static.ghost.org/v4.0.0/images/brandsettings.png\",\"width\":3456,\"height\":2338,\"cardWidth\":\"wide\",\"caption\":\"Ghost Admin โ Settings โ Branding\"}],[\"image\",{\"src\":\"https://static.ghost.org/v4.0.0/images/themesettings.png\",\"width\":3208,\"height\":1618,\"cardWidth\":\"wide\",\"caption\":\"Ghost Admin โ Settings โ Theme\"}],[\"code\",{\"code\":\"{{#post}}\\n\\n\\n {{title}} \\n \\n {{#if feature_image}}\\n \\t \\n {{/if}}\\n \\n {{content}}\\n\\n \\n{{/post}}\",\"language\":\"handlebars\",\"caption\":\"A snippet from a post template\"}]],\"markups\":[[\"a\",[\"href\",\"__GHOST_URL__/welcome/\"]],[\"strong\"],[\"em\"],[\"a\",[\"href\",\"https://ghost.org/themes/\"]],[\"a\",[\"href\",\"https://github.com/tryghost/casper/\"]],[\"a\",[\"href\",\"https://ghost.org/docs/themes/\"]]],\"sections\":[[1,\"p\",[[0,[],0,\"As discussed in the \"],[0,[0],1,\"introduction\"],[0,[],0,\" post, one of the best things about Ghost is just how much you can customize to turn your site into something unique. Everything about your layout and design can be changed, so you're not stuck with yet another clone of a social network profile.\"]]],[1,\"p\",[[0,[],0,\"How far you want to go with customization is completely up to you, there's no right or wrong approach! The majority of people use one of Ghost's built-in themes to get started, and then progress to something more bespoke later on as their site grows. \"]]],[1,\"p\",[[0,[],0,\"The best way to get started is with Ghost's branding settings, where you can set up colors, images and logos to fit with your brand.\"]]],[10,0],[1,\"p\",[[0,[],0,\"Any Ghost theme that's up to date and compatible with Ghost 4.0 and higher will reflect your branding settings in the preview window, so you can see what your site will look like as you experiment with different options.\"]]],[1,\"p\",[[0,[],0,\"When selecting an accent color, try to choose something which will contrast well with white text. Many themes will use your accent color as the background for buttons, headers and navigational elements. Vibrant colors with a darker hue tend to work best, as a general rule.\"]]],[1,\"h2\",[[0,[],0,\"Installing Ghost themes\"]]],[1,\"p\",[[0,[],0,\"By default, new sites are created with Ghost's friendly publication theme, called Casper. Everything in Casper is optimized to work for the most common types of blog, newsletter and publication that people create with Ghost โ so it's a perfect place to start.\"]]],[1,\"p\",[[0,[],0,\"However, there are hundreds of different themes available to install, so you can pick out a look and feel that suits you best.\"]]],[10,1],[1,\"p\",[[0,[],0,\"Inside Ghost's theme settings you'll find 4 more official themes that can be directly installed and activated. Each theme is suited to slightly different use-cases.\"]]],[3,\"ul\",[[[0,[1],1,\"Casper\"],[0,[],0,\" \"],[0,[2],1,\"(default)\"],[0,[],0,\" โ Made for all sorts of blogs and newsletters\"]],[[0,[1],1,\"Edition\"],[0,[],0,\" โ A beautiful minimal template for newsletter authors\"]],[[0,[1],1,\"Alto\"],[0,[],0,\" โ A slick news/magazine style design for creators\"]],[[0,[1],1,\"London\"],[0,[],0,\" โ A light photography theme with a bold grid\"]],[[0,[1],1,\"Ease\"],[0,[],0,\" โ A library theme for organizing large content archives\"]]]],[1,\"p\",[[0,[],0,\"And if none of those feel quite right, head on over to the \"],[0,[3],1,\"Ghost Marketplace\"],[0,[],0,\", where you'll find a huge variety of both free and premium themes.\"]]],[1,\"h2\",[[0,[],0,\"Building something custom\"]]],[1,\"p\",[[0,[],0,\"Finally, if you want something completely bespoke for your site, you can always build a custom theme from scratch and upload it to your site.\"]]],[1,\"p\",[[0,[],0,\"Ghost's theming template files are very easy to work with, and can be picked up in the space of a few hours by anyone who has just a little bit of knowledge of HTML and CSS. Templates from other platforms can also be ported to Ghost with relatively little effort.\"]]],[1,\"p\",[[0,[],0,\"If you want to take a quick look at the theme syntax to see what it's like, you can \"],[0,[4],1,\"browse through the files of the default Casper theme\"],[0,[],0,\". We've added tons of inline code comments to make it easy to learn, and the structure is very readable.\"]]],[10,2],[1,\"p\",[[0,[],0,\"See? Not that scary! But still completely optional. \"]]],[1,\"p\",[[0,[],0,\"If you're interested in creating your own Ghost theme, check out our extensive \"],[0,[5],1,\"theme documentation\"],[0,[],0,\" for a full guide to all the different template variables and helpers which are available.\"]]]],\"ghostVersion\":\"4.0\"}",
+ "mobiledoc": "{\"version\":\"0.3.1\",\"atoms\":[],\"cards\":[[\"image\",{\"src\":\"https://static.ghost.org/v4.0.0/images/brandsettings.png\",\"width\":3456,\"height\":2338,\"cardWidth\":\"wide\",\"caption\":\"Ghost Admin โ Settings โ Branding\"}],[\"image\",{\"src\":\"https://static.ghost.org/v4.0.0/images/themesettings.png\",\"width\":3208,\"height\":1618,\"cardWidth\":\"wide\",\"caption\":\"Ghost Admin โ Settings โ Theme\"}],[\"code\",{\"code\":\"{{#post}}\\n\\n\\n {{title}} \\n \\n {{#if feature_image}}\\n \\t \\n {{/if}}\\n \\n {{content}}\\n\\n \\n{{/post}}\",\"language\":\"handlebars\",\"caption\":\"A snippet from a post template\"}]],\"markups\":[[\"a\",[\"href\",\"__GHOST_URL__/welcome/\"]],[\"strong\"],[\"em\"],[\"a\",[\"href\",\"https://ghost.org/themes/\"]],[\"a\",[\"href\",\"https://github.com/tryghost/casper/\"]],[\"a\",[\"href\",\"https://docs.ghost.org/themes/\"]]],\"sections\":[[1,\"p\",[[0,[],0,\"As discussed in the \"],[0,[0],1,\"introduction\"],[0,[],0,\" post, one of the best things about Ghost is just how much you can customize to turn your site into something unique. Everything about your layout and design can be changed, so you're not stuck with yet another clone of a social network profile.\"]]],[1,\"p\",[[0,[],0,\"How far you want to go with customization is completely up to you, there's no right or wrong approach! The majority of people use one of Ghost's built-in themes to get started, and then progress to something more bespoke later on as their site grows. \"]]],[1,\"p\",[[0,[],0,\"The best way to get started is with Ghost's branding settings, where you can set up colors, images and logos to fit with your brand.\"]]],[10,0],[1,\"p\",[[0,[],0,\"Any Ghost theme that's up to date and compatible with Ghost 4.0 and higher will reflect your branding settings in the preview window, so you can see what your site will look like as you experiment with different options.\"]]],[1,\"p\",[[0,[],0,\"When selecting an accent color, try to choose something which will contrast well with white text. Many themes will use your accent color as the background for buttons, headers and navigational elements. Vibrant colors with a darker hue tend to work best, as a general rule.\"]]],[1,\"h2\",[[0,[],0,\"Installing Ghost themes\"]]],[1,\"p\",[[0,[],0,\"By default, new sites are created with Ghost's friendly publication theme, called Casper. Everything in Casper is optimized to work for the most common types of blog, newsletter and publication that people create with Ghost โ so it's a perfect place to start.\"]]],[1,\"p\",[[0,[],0,\"However, there are hundreds of different themes available to install, so you can pick out a look and feel that suits you best.\"]]],[10,1],[1,\"p\",[[0,[],0,\"Inside Ghost's theme settings you'll find 4 more official themes that can be directly installed and activated. Each theme is suited to slightly different use-cases.\"]]],[3,\"ul\",[[[0,[1],1,\"Casper\"],[0,[],0,\" \"],[0,[2],1,\"(default)\"],[0,[],0,\" โ Made for all sorts of blogs and newsletters\"]],[[0,[1],1,\"Edition\"],[0,[],0,\" โ A beautiful minimal template for newsletter authors\"]],[[0,[1],1,\"Alto\"],[0,[],0,\" โ A slick news/magazine style design for creators\"]],[[0,[1],1,\"London\"],[0,[],0,\" โ A light photography theme with a bold grid\"]],[[0,[1],1,\"Ease\"],[0,[],0,\" โ A library theme for organizing large content archives\"]]]],[1,\"p\",[[0,[],0,\"And if none of those feel quite right, head on over to the \"],[0,[3],1,\"Ghost Marketplace\"],[0,[],0,\", where you'll find a huge variety of both free and premium themes.\"]]],[1,\"h2\",[[0,[],0,\"Building something custom\"]]],[1,\"p\",[[0,[],0,\"Finally, if you want something completely bespoke for your site, you can always build a custom theme from scratch and upload it to your site.\"]]],[1,\"p\",[[0,[],0,\"Ghost's theming template files are very easy to work with, and can be picked up in the space of a few hours by anyone who has just a little bit of knowledge of HTML and CSS. Templates from other platforms can also be ported to Ghost with relatively little effort.\"]]],[1,\"p\",[[0,[],0,\"If you want to take a quick look at the theme syntax to see what it's like, you can \"],[0,[4],1,\"browse through the files of the default Casper theme\"],[0,[],0,\". We've added tons of inline code comments to make it easy to learn, and the structure is very readable.\"]]],[10,2],[1,\"p\",[[0,[],0,\"See? Not that scary! But still completely optional. \"]]],[1,\"p\",[[0,[],0,\"If you're interested in creating your own Ghost theme, check out our extensive \"],[0,[5],1,\"theme documentation\"],[0,[],0,\" for a full guide to all the different template variables and helpers which are available.\"]]]],\"ghostVersion\":\"4.0\"}",
"feature_image": "https://static.ghost.org/v4.0.0/images/publishing-options.png",
"featured": false,
"type": "post",
diff --git a/koenig/kg-default-nodes/src/nodes/signup/signup-renderer.ts b/koenig/kg-default-nodes/src/nodes/signup/signup-renderer.ts
index f346e00b37e..f8731a32cf0 100644
--- a/koenig/kg-default-nodes/src/nodes/signup/signup-renderer.ts
+++ b/koenig/kg-default-nodes/src/nodes/signup/signup-renderer.ts
@@ -2,7 +2,7 @@ import {addCreateDocumentOption} from '../../utils/add-create-document-option.js
import type {ExportDOMOptions} from '../../export-dom.js';
import {getFirstHtmlElement} from '../../utils/get-first-html-element.js';
-// ref https://ghost.org/docs/themes/members#signup-forms
+// ref https://docs.ghost.org/themes/members#signup-forms
interface SignupNodeData {
alignment: string;
diff --git a/koenig/koenig-lexical/demo/DemoApp.tsx b/koenig/koenig-lexical/demo/DemoApp.tsx
index 20c00ed83a6..a70f1b866f8 100644
--- a/koenig/koenig-lexical/demo/DemoApp.tsx
+++ b/koenig/koenig-lexical/demo/DemoApp.tsx
@@ -86,7 +86,7 @@ const defaultCardConfig = {
].filter(item => item.title.toLowerCase().includes(term.toLowerCase()));
const pages = [
- {id: '3', groupName: 'Pages', title: 'How to update Ghost', url: 'https://ghost.org/docs/update/'}
+ {id: '3', groupName: 'Pages', title: 'How to update Ghost', url: 'https://docs.ghost.org/update/'}
].filter(item => item.title.toLowerCase().includes(term.toLowerCase()));
const tags = [
diff --git a/koenig/koenig-lexical/src/components/ui/file-selectors/Gif/Error.tsx b/koenig/koenig-lexical/src/components/ui/file-selectors/Gif/Error.tsx
index b77144b2716..24017fb311d 100644
--- a/koenig/koenig-lexical/src/components/ui/file-selectors/Gif/Error.tsx
+++ b/koenig/koenig-lexical/src/components/ui/file-selectors/Gif/Error.tsx
@@ -13,7 +13,7 @@ export function Error({error}) {
return (
The GIF API key is not valid. Please check your configuration by following our
- documentation here .
+ documentation here .
);
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b209b6c69b3..7fca5ba3187 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -195,6 +195,9 @@ catalogs:
'@tryghost/tpl':
specifier: 2.3.9
version: 2.3.9
+ '@tryghost/validator':
+ specifier: 3.2.10
+ version: 3.2.10
'@types/express':
specifier: 4.17.25
version: 4.17.25
@@ -2408,8 +2411,8 @@ importers:
specifier: 5.2.6
version: 5.2.6
'@tryghost/validator':
- specifier: 0.2.22
- version: 0.2.22
+ specifier: 'catalog:'
+ version: 3.2.10
'@tryghost/version':
specifier: 2.3.2
version: 2.3.2
@@ -9571,15 +9574,9 @@ packages:
'@tryghost/url-utils@5.2.7':
resolution: {integrity: sha512-enJ084BtVwsr+q+1wz2hy/jjooBfDLh4FS4g2o8n1ZAgGAly4mch6w7IpDX6MjRcy6an/S1i/Jo3tMsHa2igfw==}
- '@tryghost/validator@0.2.22':
- resolution: {integrity: sha512-dmobNVEKXMi3K4OdAXLBFwa78hVgy8cYvCPJgfV4h3NtYIyRHqwARr3upT3/ASpVBpKGVBu7XexfSv+tibzsuQ==}
-
'@tryghost/validator@3.2.10':
resolution: {integrity: sha512-mk4bexOTmO4xaGiTvHXwCQIH5okcR3XOA3zF2vSMu89lpwZ0QJPXEaooHZUdLLBGOwuigz0iLVyiNeC58OAyrg==}
- '@tryghost/validator@3.2.9':
- resolution: {integrity: sha512-dhb8ix2Xd1TVCjpwAJ87MRO/39m1fTl72dfqLbjnmA8r/vgA59SLVUF+BimH3DHcN6vDldwJlDlfJ4nHl5NEaA==}
-
'@tryghost/version@2.3.10':
resolution: {integrity: sha512-4KHPqEZwhyixUFfDiNCoJ6nCjmLrX5VwqrPcAsNQs82W2chkaysvjZU9rXFNULpapRcYu0Y7xX/Sn2JJDlbriQ==}
@@ -22292,10 +22289,6 @@ packages:
resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==}
engines: {node: '>= 0.10'}
- validator@7.2.0:
- resolution: {integrity: sha512-c8NGTUYeBEcUIGeMppmNVKHE7wwfm3mYbNZxV+c5mlv9fDHI7Ad3p07qfNrn/CvpdkK2k61fOLRO2sTEhgQXmg==}
- engines: {node: '>= 0.10'}
-
value-or-function@4.0.0:
resolution: {integrity: sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==}
engines: {node: '>= 10.13.0'}
@@ -28813,7 +28806,7 @@ snapshots:
'@tryghost/errors': 3.3.9
'@tryghost/promise': 2.3.9
'@tryghost/tpl': 2.3.9
- '@tryghost/validator': 3.2.9
+ '@tryghost/validator': 3.2.10
lodash: 4.18.1
transitivePeerDependencies:
- supports-color
@@ -29215,7 +29208,7 @@ snapshots:
'@tryghost/request@4.0.2':
dependencies:
'@tryghost/errors': 3.3.9
- '@tryghost/validator': 3.2.9
+ '@tryghost/validator': 3.2.10
'@tryghost/version': 2.3.9
cacheable-lookup: 7.0.0
got: 15.1.0
@@ -29317,14 +29310,6 @@ snapshots:
remark-footnotes: 1.0.0
unist-util-visit: 2.0.3
- '@tryghost/validator@0.2.22':
- dependencies:
- '@tryghost/errors': 3.3.9
- '@tryghost/tpl': 0.1.40
- lodash: 4.18.1
- moment-timezone: 0.5.45
- validator: 7.2.0
-
'@tryghost/validator@3.2.10':
dependencies:
'@tryghost/errors': 3.3.9
@@ -29333,14 +29318,6 @@ snapshots:
moment-timezone: 0.5.45
validator: 13.15.35
- '@tryghost/validator@3.2.9':
- dependencies:
- '@tryghost/errors': 3.3.9
- '@tryghost/tpl': 2.3.9
- lodash: 4.18.1
- moment-timezone: 0.5.45
- validator: 13.15.35
-
'@tryghost/version@2.3.10':
dependencies:
'@tryghost/root-utils': 2.3.10
@@ -30454,9 +30431,9 @@ snapshots:
obug: 2.1.3
std-env: 4.1.0
tinyrainbow: 3.1.0
- vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@30.0.1(@noble/hashes@1.8.0))(msw@2.14.6(@types/node@26.0.0)(@typescript/typescript6@6.0.2))(vite@8.1.3(@types/node@26.0.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.12)(yaml@2.9.0))
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@30.0.1(@noble/hashes@1.8.0))(msw@2.14.6(@types/node@22.20.1)(@typescript/typescript6@6.0.2))(vite@8.1.3(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.12)(yaml@2.9.0))
optionalDependencies:
- '@vitest/browser': 4.1.10(msw@2.14.6(@types/node@26.0.0)(@typescript/typescript6@6.0.2))(vite@8.1.3(@types/node@26.0.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)
+ '@vitest/browser': 4.1.10(msw@2.14.6(@types/node@22.20.1)(@typescript/typescript6@6.0.2))(vite@8.1.3(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.12)(yaml@2.9.0))(vitest@4.1.10)
'@vitest/expect@3.2.4':
dependencies:
@@ -30537,7 +30514,7 @@ snapshots:
sirv: 3.0.2
tinyglobby: 0.2.17
tinyrainbow: 3.1.0
- vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@30.0.1(@noble/hashes@1.8.0))(msw@2.14.6(@types/node@26.0.0)(@typescript/typescript6@6.0.2))(vite@8.1.3(@types/node@26.0.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.12)(yaml@2.9.0))
+ vitest: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(@vitest/ui@4.1.10)(jsdom@30.0.1(@noble/hashes@1.8.0))(msw@2.14.6(@types/node@22.20.1)(@typescript/typescript6@6.0.2))(vite@8.1.3(@types/node@22.20.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.12)(yaml@2.9.0))
'@vitest/utils@3.2.4':
dependencies:
@@ -46727,8 +46704,6 @@ snapshots:
validator@13.15.35: {}
- validator@7.2.0: {}
-
value-or-function@4.0.0: {}
vary@1.1.2: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index e78b7cff035..d10f0ae55b6 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -223,6 +223,7 @@ catalog:
cron-validate: 1.4.5
'@types/stoppable': 1.1.3
'@types/express-brute': 1.0.6
+ '@tryghost/validator': 3.2.10
catalogs:
react17: