Skip to content

Commit eaeee31

Browse files
feat(endpoint-micropub): channels
1 parent 825fee1 commit eaeee31

File tree

7 files changed

+128
-7
lines changed

7 files changed

+128
-7
lines changed

packages/endpoint-micropub/lib/config.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
*/
77
export const getConfig = (application, publication) => {
88
const { mediaEndpoint, url } = application;
9-
const { categories, postTypes, syndicationTargets } = publication;
9+
const { categories, channels, postTypes, syndicationTargets } = publication;
1010

1111
// Supported queries
1212
const q = [
1313
"category",
14+
"channel",
1415
"config",
1516
"media-endpoint",
1617
"post-types",
@@ -28,6 +29,10 @@ export const getConfig = (application, publication) => {
2829

2930
return {
3031
categories,
32+
channels: Object.entries(channels).map(([uid, channel]) => ({
33+
uid,
34+
name: channel.name,
35+
})),
3136
"media-endpoint": mediaEndpoint,
3237
"post-types": Object.values(postTypes).map((postType) => ({
3338
type: postType.type,

packages/endpoint-micropub/lib/controllers/query.js

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export const queryController = async (request, response, next) => {
2525
// `category` param is used to query `categories` configuration property
2626
q = q === "category" ? "categories" : String(q);
2727

28+
// `channel` param is used to query `channels` configuration property
29+
q = q === "channel" ? "channels" : String(q);
30+
2831
switch (q) {
2932
case "config": {
3033
response.json(config);

packages/endpoint-micropub/lib/jf2.js

+44-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const mf2ToJf2 = async (body, requestReferences) => {
7171
* @returns {object} Normalised JF2 properties
7272
*/
7373
export const normaliseProperties = (publication, properties, timeZone) => {
74-
const { me, slugSeparator } = publication;
74+
const { channels, me, slugSeparator } = publication;
7575

7676
properties.published = getDate(timeZone, properties.published);
7777

@@ -101,6 +101,11 @@ export const normaliseProperties = (publication, properties, timeZone) => {
101101

102102
properties.slug = getSlugProperty(properties, slugSeparator);
103103

104+
const publicationHasChannels = channels && Object.keys(channels).length > 0;
105+
if (publicationHasChannels) {
106+
properties["mp-channel"] = getChannelProperty(properties, channels);
107+
}
108+
104109
if (properties["mp-syndicate-to"]) {
105110
properties["mp-syndicate-to"] = toArray(properties["mp-syndicate-to"]);
106111
}
@@ -127,6 +132,44 @@ export const getAudioProperty = (properties, me) => {
127132
}));
128133
};
129134

135+
/**
136+
* Get channel property.
137+
*
138+
* If a publication has configured channels, but no channel has been selected,
139+
* the default channel is used.
140+
*
141+
* If `mp-channel` provides a UID that does not appear in the publication’s
142+
* channels, the default channel is used.
143+
*
144+
* The first item in a publication’s configured channels is considered the
145+
* default channel.
146+
* @param {object} properties - JF2 properties
147+
* @param {object} channels - Publication channels
148+
* @returns {Array} `mp-channel` property
149+
* @see {@link https://github.com/indieweb/micropub-extensions/issues/40}
150+
*/
151+
export const getChannelProperty = (properties, channels) => {
152+
channels = Object.keys(channels);
153+
const mpChannel = properties["mp-channel"];
154+
const providedChannels = Array.isArray(mpChannel) ? mpChannel : [mpChannel];
155+
const selectedChannels = new Set();
156+
157+
// Only select channels that have been configured
158+
for (const uid of providedChannels) {
159+
if (channels.includes(uid)) {
160+
selectedChannels.add(uid);
161+
}
162+
}
163+
164+
// If no channels provided, use default channel UID
165+
if (selectedChannels.size === 0) {
166+
const defaultChannel = channels[0];
167+
selectedChannels.add(defaultChannel);
168+
}
169+
170+
return toArray([...selectedChannels]);
171+
};
172+
130173
/**
131174
* Get content property.
132175
*

packages/endpoint-micropub/lib/post-data.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,14 @@ export const postData = {
4848
typeConfig.post.path,
4949
properties,
5050
application,
51+
publication,
52+
);
53+
const url = await renderPath(
54+
typeConfig.post.url,
55+
properties,
56+
application,
57+
publication,
5158
);
52-
const url = await renderPath(typeConfig.post.url, properties, application);
5359
properties.url = getCanonicalUrl(url, me);
5460

5561
// Post status
@@ -144,11 +150,13 @@ export const postData = {
144150
typeConfig.post.path,
145151
properties,
146152
application,
153+
publication,
147154
);
148155
const updatedUrl = await renderPath(
149156
typeConfig.post.url,
150157
properties,
151158
application,
159+
publication,
152160
);
153161
properties.url = getCanonicalUrl(updatedUrl, me);
154162

@@ -208,6 +216,7 @@ export const postData = {
208216
typeConfig.post.path,
209217
properties,
210218
application,
219+
publication,
211220
);
212221

213222
// Update data in posts collection
@@ -249,6 +258,7 @@ export const postData = {
249258
typeConfig.post.path,
250259
properties,
251260
application,
261+
publication,
252262
);
253263

254264
// Post status

packages/endpoint-micropub/lib/utils.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export const getPostTemplateProperties = (properties) => {
5050
const templateProperties = structuredClone(properties);
5151

5252
for (let key in templateProperties) {
53+
// Pass mp-channel to template as channel
54+
if (key === "mp-channel") {
55+
templateProperties.channel = templateProperties["mp-channel"];
56+
}
57+
5358
// Remove server commands from post template properties
5459
if (key.startsWith("mp-")) {
5560
delete templateProperties[key];
@@ -78,12 +83,19 @@ export const relativeMediaPath = (url, me) =>
7883
* @param {string} path - URI template path
7984
* @param {object} properties - JF2 properties
8085
* @param {object} application - Application configuration
86+
* @param {object} publication - Publication configuration
8187
* @returns {Promise<string>} Path
8288
*/
83-
export const renderPath = async (path, properties, application) => {
89+
export const renderPath = async (
90+
path,
91+
properties,
92+
application,
93+
publication,
94+
) => {
8495
const dateObject = new Date(properties.published);
8596
const serverTimeZone = getTimeZoneDesignator();
8697
const { locale, timeZone } = application;
98+
const { slugSeparator } = publication;
8799
let tokens = {};
88100

89101
// Add date tokens
@@ -105,6 +117,13 @@ export const renderPath = async (path, properties, application) => {
105117
// Add slug token
106118
tokens.slug = properties.slug;
107119

120+
// Add channel token
121+
if (properties.channel) {
122+
tokens.channel = Array.isArray(properties.channel)
123+
? properties.channel.join(slugSeparator)
124+
: properties.channel;
125+
}
126+
108127
// Populate URI template path with properties
109128
path = supplant(path, tokens);
110129

packages/endpoint-micropub/test/unit/jf2.js

+32
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
formEncodedToJf2,
77
mf2ToJf2,
88
getAudioProperty,
9+
getChannelProperty,
910
getContentProperty,
1011
getLocationProperty,
1112
getPhotoProperty,
@@ -17,6 +18,14 @@ import {
1718

1819
await mockAgent("endpoint-micropub");
1920
const publication = {
21+
channels: {
22+
posts: {
23+
name: "Posts",
24+
},
25+
pages: {
26+
name: "Pages",
27+
},
28+
},
2029
slugSeparator: "-",
2130
syndicationTargets: [
2231
{
@@ -132,6 +141,28 @@ describe("endpoint-micropub/lib/jf2", () => {
132141
]);
133142
});
134143

144+
it("Gets normalised channel property", () => {
145+
const one = { "mp-channel": "posts" };
146+
const none = { "mp-channel": "" };
147+
const many = { "mp-channel": ["posts", "pages"] };
148+
const { channels } = publication;
149+
150+
assert.deepEqual(getChannelProperty(one, channels), ["posts"]);
151+
assert.deepEqual(getChannelProperty(none, channels), ["posts"]);
152+
assert.deepEqual(getChannelProperty(many, channels), ["posts", "pages"]);
153+
});
154+
155+
it("Gets normalised channel property, returning default channel", () => {
156+
const oneMissing = { "mp-channel": "foo" };
157+
const manyMissing = { "mp-channel": ["foo", "bar"] };
158+
const someMissing = { "mp-channel": ["foo", "bar", "pages"] };
159+
const { channels } = publication;
160+
161+
assert.deepEqual(getChannelProperty(oneMissing, channels), ["posts"]);
162+
assert.deepEqual(getChannelProperty(manyMissing, channels), ["posts"]);
163+
assert.deepEqual(getChannelProperty(someMissing, channels), ["pages"]);
164+
});
165+
135166
it("Gets text and HTML values from `content` property", () => {
136167
const properties = JSON.parse(
137168
getFixture("jf2/article-content-provided-html-text.jf2"),
@@ -400,6 +431,7 @@ describe("endpoint-micropub/lib/jf2", () => {
400431
);
401432
const result = normaliseProperties(publication, properties, "UTC");
402433

434+
assert.equal(result.channels, undefined);
403435
assert.equal(result.type, "entry");
404436
assert.equal(result.name, "What I had for lunch");
405437
assert.equal(result.slug, "what-i-had-for-lunch");

packages/endpoint-micropub/test/unit/utils.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ describe("endpoint-media/lib/utils", () => {
6262
});
6363

6464
it("Renders path from URI template and properties", async () => {
65-
const template = "{yyyy}/{MM}/{slug}";
65+
const template = "{channel}/{yyyy}/{MM}/{slug}";
6666
const properties = {
6767
published: "2020-01-01",
68+
channel: ["foo", "bar"],
6869
slug: "foo",
6970
};
7071
const application = {
@@ -83,9 +84,17 @@ describe("endpoint-media/lib/utils", () => {
8384
},
8485
},
8586
};
86-
const result = await renderPath(template, properties, application);
87+
const publication = {
88+
slugSeparator: "_",
89+
};
90+
const result = await renderPath(
91+
template,
92+
properties,
93+
application,
94+
publication,
95+
);
8796

88-
assert.match(result, /\d{4}\/\d{2}\/foo/);
97+
assert.match(result, /foo_bar\/\d{4}\/\d{2}\/foo/);
8998
});
9099

91100
it("Convert string to array if not already an array", () => {

0 commit comments

Comments
 (0)