@rmdes/indiekit-endpoint-posts 1.0.0-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/assets/icon.svg +4 -0
- package/includes/@indiekit-endpoint-posts-syndicate.njk +21 -0
- package/includes/@indiekit-endpoint-posts-widget.njk +14 -0
- package/includes/post-types/category-field.njk +11 -0
- package/includes/post-types/category.njk +4 -0
- package/includes/post-types/content-field.njk +17 -0
- package/includes/post-types/content.njk +3 -0
- package/includes/post-types/featured-field.njk +30 -0
- package/includes/post-types/featured.njk +8 -0
- package/includes/post-types/geo-field.njk +8 -0
- package/includes/post-types/location-field.njk +42 -0
- package/includes/post-types/location.njk +12 -0
- package/includes/post-types/mp-channel.njk +4 -0
- package/includes/post-types/name-field.njk +7 -0
- package/includes/post-types/published.njk +4 -0
- package/includes/post-types/summary-field.njk +17 -0
- package/includes/post-types/summary.njk +4 -0
- package/index.js +108 -0
- package/lib/controllers/delete.js +52 -0
- package/lib/controllers/form.js +121 -0
- package/lib/controllers/new.js +87 -0
- package/lib/controllers/post.js +51 -0
- package/lib/controllers/posts.js +96 -0
- package/lib/endpoint.js +53 -0
- package/lib/middleware/post-data.js +98 -0
- package/lib/middleware/validation.js +23 -0
- package/lib/status-types.js +32 -0
- package/lib/utils.js +201 -0
- package/locales/de.json +127 -0
- package/locales/en.json +127 -0
- package/locales/es-419.json +127 -0
- package/locales/es.json +127 -0
- package/locales/fr.json +127 -0
- package/locales/hi.json +127 -0
- package/locales/id.json +127 -0
- package/locales/it.json +127 -0
- package/locales/nl.json +127 -0
- package/locales/pl.json +127 -0
- package/locales/pt-BR.json +127 -0
- package/locales/pt.json +127 -0
- package/locales/sr.json +127 -0
- package/locales/sv.json +127 -0
- package/locales/zh-Hans-CN.json +127 -0
- package/package.json +55 -0
- package/views/new.njk +21 -0
- package/views/post-delete.njk +17 -0
- package/views/post-form.njk +114 -0
- package/views/post.njk +39 -0
- package/views/posts.njk +13 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { checkScope } from "@indiekit/endpoint-micropub/lib/scope.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* View published post
|
|
7
|
+
* @type {import("express").RequestHandler}
|
|
8
|
+
*/
|
|
9
|
+
export const postController = async (request, response) => {
|
|
10
|
+
const { draftMode, postName, postsPath, postStatus, properties, scope } =
|
|
11
|
+
response.locals;
|
|
12
|
+
|
|
13
|
+
const postEditable = draftMode ? postStatus === "draft" : true;
|
|
14
|
+
|
|
15
|
+
response.render("post", {
|
|
16
|
+
title: postName,
|
|
17
|
+
parent: {
|
|
18
|
+
href: postsPath,
|
|
19
|
+
text: response.locals.__("posts.posts.title"),
|
|
20
|
+
},
|
|
21
|
+
actions: [
|
|
22
|
+
scope &&
|
|
23
|
+
checkScope(scope, "update") &&
|
|
24
|
+
!properties.deleted &&
|
|
25
|
+
postEditable
|
|
26
|
+
? {
|
|
27
|
+
href: path.join(request.baseUrl + request.path, "/update"),
|
|
28
|
+
icon: "updatePost",
|
|
29
|
+
text: response.locals.__("posts.update.action"),
|
|
30
|
+
}
|
|
31
|
+
: {},
|
|
32
|
+
scope && checkScope(scope, "delete") && !properties.deleted
|
|
33
|
+
? {
|
|
34
|
+
classes: "actions__link--warning",
|
|
35
|
+
href: path.join(request.baseUrl + request.path, "/delete"),
|
|
36
|
+
icon: "delete",
|
|
37
|
+
text: response.locals.__("posts.delete.action"),
|
|
38
|
+
}
|
|
39
|
+
: {},
|
|
40
|
+
scope && checkScope(scope, "undelete") && properties.deleted
|
|
41
|
+
? {
|
|
42
|
+
href: path.join(request.baseUrl + request.path, "/undelete"),
|
|
43
|
+
icon: "undelete",
|
|
44
|
+
text: response.locals.__("posts.undelete.action"),
|
|
45
|
+
}
|
|
46
|
+
: {},
|
|
47
|
+
],
|
|
48
|
+
redirectUri: path.join(request.baseUrl, request.params.uid),
|
|
49
|
+
success: request.query.success,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { checkScope } from "@indiekit/endpoint-micropub/lib/scope.js";
|
|
4
|
+
import { excerpt } from "@indiekit/util";
|
|
5
|
+
import { mf2tojf2 } from "@paulrobertlloyd/mf2tojf2";
|
|
6
|
+
|
|
7
|
+
import { endpoint } from "../endpoint.js";
|
|
8
|
+
import { statusTypes } from "../status-types.js";
|
|
9
|
+
import { getPostStatusBadges, getPostName, getPhotoUrl } from "../utils.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* List published posts
|
|
13
|
+
* @type {import("express").RequestHandler}
|
|
14
|
+
*/
|
|
15
|
+
export const postsController = async (request, response, next) => {
|
|
16
|
+
try {
|
|
17
|
+
const { application, publication } = request.app.locals;
|
|
18
|
+
const { access_token, scope } = request.session;
|
|
19
|
+
const { after, before, success } = request.query;
|
|
20
|
+
const limit = Number(request.query.limit) || 12;
|
|
21
|
+
|
|
22
|
+
const micropubUrl = new URL(application.micropubEndpoint);
|
|
23
|
+
micropubUrl.searchParams.append("q", "source");
|
|
24
|
+
micropubUrl.searchParams.append("limit", String(limit));
|
|
25
|
+
|
|
26
|
+
if (after) {
|
|
27
|
+
micropubUrl.searchParams.append("after", String(after));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (before) {
|
|
31
|
+
micropubUrl.searchParams.append("before", String(before));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const micropubResponse = await endpoint.get(micropubUrl.href, access_token);
|
|
35
|
+
|
|
36
|
+
let posts;
|
|
37
|
+
if (micropubResponse?.items?.length > 0) {
|
|
38
|
+
const jf2 = mf2tojf2(micropubResponse);
|
|
39
|
+
const items = jf2.children || [jf2];
|
|
40
|
+
|
|
41
|
+
posts = items.map((item) => {
|
|
42
|
+
item.id = item.uid;
|
|
43
|
+
item.icon = item["post-type"];
|
|
44
|
+
item.locale = application.locale;
|
|
45
|
+
item.photo = getPhotoUrl(publication, item);
|
|
46
|
+
item.description = {
|
|
47
|
+
text:
|
|
48
|
+
item.summary ||
|
|
49
|
+
(item.content?.text &&
|
|
50
|
+
excerpt(item.content.text, 30, publication.locale)),
|
|
51
|
+
};
|
|
52
|
+
item.title = getPostName(publication, item);
|
|
53
|
+
item.url = path.join(request.baseUrl, request.path, item.uid);
|
|
54
|
+
item.badges = getPostStatusBadges(item, response);
|
|
55
|
+
|
|
56
|
+
return item;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const cursor = {};
|
|
61
|
+
|
|
62
|
+
if (micropubResponse?.paging?.after) {
|
|
63
|
+
cursor.next = {
|
|
64
|
+
href: `?after=${micropubResponse.paging.after}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (micropubResponse?.paging?.before) {
|
|
69
|
+
cursor.previous = {
|
|
70
|
+
href: `?before=${micropubResponse.paging.before}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
response.render("posts", {
|
|
75
|
+
title: response.locals.__("posts.posts.title"),
|
|
76
|
+
actions: [
|
|
77
|
+
scope && checkScope(scope, "create")
|
|
78
|
+
? {
|
|
79
|
+
href: path.join(request.baseUrl + request.path, "/new"),
|
|
80
|
+
icon: "createPost",
|
|
81
|
+
text: response.locals.__("posts.create.action"),
|
|
82
|
+
}
|
|
83
|
+
: {},
|
|
84
|
+
],
|
|
85
|
+
cursor,
|
|
86
|
+
posts,
|
|
87
|
+
limit,
|
|
88
|
+
count: micropubResponse._count,
|
|
89
|
+
parentUrl: request.baseUrl + request.path,
|
|
90
|
+
statusTypes,
|
|
91
|
+
success,
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
next(error);
|
|
95
|
+
}
|
|
96
|
+
};
|
package/lib/endpoint.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { IndiekitError } from "@indiekit/error";
|
|
2
|
+
|
|
3
|
+
export const endpoint = {
|
|
4
|
+
/**
|
|
5
|
+
* Micropub query
|
|
6
|
+
* @param {string} url - URL
|
|
7
|
+
* @param {string} accessToken - Access token
|
|
8
|
+
* @returns {Promise<object>} Response data
|
|
9
|
+
*/
|
|
10
|
+
async get(url, accessToken) {
|
|
11
|
+
const endpointResponse = await fetch(url, {
|
|
12
|
+
headers: {
|
|
13
|
+
accept: "application/json",
|
|
14
|
+
authorization: `Bearer ${accessToken}`,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (!endpointResponse.ok) {
|
|
19
|
+
throw await IndiekitError.fromFetch(endpointResponse);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const body = await endpointResponse.json();
|
|
23
|
+
|
|
24
|
+
return body;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Micropub action
|
|
29
|
+
* @param {string} url - URL
|
|
30
|
+
* @param {string} accessToken - Access token
|
|
31
|
+
* @param {object} [jsonBody] - JSON body
|
|
32
|
+
* @returns {Promise<object>} Response data
|
|
33
|
+
*/
|
|
34
|
+
async post(url, accessToken, jsonBody = false) {
|
|
35
|
+
const endpointResponse = await fetch(url, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
accept: "application/json",
|
|
39
|
+
authorization: `Bearer ${accessToken}`,
|
|
40
|
+
...(jsonBody && { "content-type": "application/json" }),
|
|
41
|
+
},
|
|
42
|
+
...(jsonBody && { body: JSON.stringify(jsonBody) }),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!endpointResponse.ok) {
|
|
46
|
+
throw await IndiekitError.fromFetch(endpointResponse);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return endpointResponse.status === 204
|
|
50
|
+
? { success_description: endpointResponse.headers.get("location") }
|
|
51
|
+
: await endpointResponse.json();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { IndiekitError } from "@indiekit/error";
|
|
4
|
+
|
|
5
|
+
import { statusTypes } from "../status-types.js";
|
|
6
|
+
import {
|
|
7
|
+
getChannelItems,
|
|
8
|
+
getGeoValue,
|
|
9
|
+
getPostName,
|
|
10
|
+
getPostProperties,
|
|
11
|
+
getSyndicateToItems,
|
|
12
|
+
} from "../utils.js";
|
|
13
|
+
|
|
14
|
+
export const postData = {
|
|
15
|
+
create(request, response, next) {
|
|
16
|
+
const { publication } = request.app.locals;
|
|
17
|
+
const { access_token, scope } = request.session;
|
|
18
|
+
|
|
19
|
+
// Create new post object with default values
|
|
20
|
+
const postType = request.query.type || "note";
|
|
21
|
+
const properties = request.body || {};
|
|
22
|
+
|
|
23
|
+
// Get post type config
|
|
24
|
+
const { name, fields, h } = publication.postTypes[postType];
|
|
25
|
+
|
|
26
|
+
// Only select ‘checked’ syndication targets on first view
|
|
27
|
+
const checkTargets = Object.entries(properties).length === 0;
|
|
28
|
+
|
|
29
|
+
response.locals = {
|
|
30
|
+
accessToken: access_token,
|
|
31
|
+
action: "create",
|
|
32
|
+
channelItems: getChannelItems(publication),
|
|
33
|
+
fields,
|
|
34
|
+
name,
|
|
35
|
+
postsPath: path.dirname(request.baseUrl + request.path),
|
|
36
|
+
postType,
|
|
37
|
+
properties,
|
|
38
|
+
scope,
|
|
39
|
+
showAdvancedOptions: false,
|
|
40
|
+
syndicationTargetItems: getSyndicateToItems(publication, checkTargets),
|
|
41
|
+
type: h,
|
|
42
|
+
...response.locals,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
next();
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async read(request, response, next) {
|
|
49
|
+
try {
|
|
50
|
+
const { application, publication } = request.app.locals;
|
|
51
|
+
const { action, uid } = request.params;
|
|
52
|
+
const { access_token, scope } = request.session;
|
|
53
|
+
|
|
54
|
+
const properties = await getPostProperties(
|
|
55
|
+
uid,
|
|
56
|
+
application.micropubEndpoint,
|
|
57
|
+
access_token,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!properties) {
|
|
61
|
+
throw IndiekitError.notFound(response.locals.__("NotFoundError.page"));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const allDay = properties?.start && !properties.start.includes("T");
|
|
65
|
+
const geo = properties?.location && getGeoValue(properties.location);
|
|
66
|
+
const postType = properties["post-type"];
|
|
67
|
+
|
|
68
|
+
// Get post type config
|
|
69
|
+
const { name, fields, h } = publication.postTypes[postType];
|
|
70
|
+
|
|
71
|
+
response.locals = {
|
|
72
|
+
accessToken: access_token,
|
|
73
|
+
action: action || "create",
|
|
74
|
+
allDay,
|
|
75
|
+
channelItems: getChannelItems(publication),
|
|
76
|
+
draftMode: scope?.includes("draft"),
|
|
77
|
+
fields,
|
|
78
|
+
geo,
|
|
79
|
+
h,
|
|
80
|
+
name,
|
|
81
|
+
postName: getPostName(publication, properties),
|
|
82
|
+
postsPath: path.dirname(request.baseUrl + request.path),
|
|
83
|
+
postStatus: properties["post-status"],
|
|
84
|
+
postType,
|
|
85
|
+
properties,
|
|
86
|
+
scope,
|
|
87
|
+
showAdvancedOptions: true,
|
|
88
|
+
syndicationTargetItems: getSyndicateToItems(publication),
|
|
89
|
+
statusTypes,
|
|
90
|
+
...response.locals,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
next();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
next(error);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { check, checkSchema } from "express-validator";
|
|
2
|
+
|
|
3
|
+
export const validate = {
|
|
4
|
+
async form(request, response, next) {
|
|
5
|
+
const { validationSchemas } = request.app.locals;
|
|
6
|
+
const validations = [];
|
|
7
|
+
|
|
8
|
+
for (const schema of [Object.fromEntries(validationSchemas)]) {
|
|
9
|
+
validations.push(checkSchema(schema));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (let validation of validations) {
|
|
13
|
+
await validation.run(request);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
next();
|
|
17
|
+
},
|
|
18
|
+
new: [
|
|
19
|
+
check("type")
|
|
20
|
+
.exists()
|
|
21
|
+
.withMessage((value, { req }) => req.__("posts.error.type.empty")),
|
|
22
|
+
],
|
|
23
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const statusTypes = {
|
|
2
|
+
deleted: {
|
|
3
|
+
color: "red",
|
|
4
|
+
text: "posts.status.deleted",
|
|
5
|
+
},
|
|
6
|
+
draft: {
|
|
7
|
+
color: "offset",
|
|
8
|
+
text: "posts.status.draft",
|
|
9
|
+
},
|
|
10
|
+
private: {
|
|
11
|
+
color: "offset-purple",
|
|
12
|
+
icon: "publicOff",
|
|
13
|
+
text: "posts.status.private",
|
|
14
|
+
},
|
|
15
|
+
public: {
|
|
16
|
+
color: "offset-purple",
|
|
17
|
+
icon: "public",
|
|
18
|
+
text: "posts.status.public",
|
|
19
|
+
},
|
|
20
|
+
published: {
|
|
21
|
+
color: "purple",
|
|
22
|
+
text: "posts.status.published",
|
|
23
|
+
},
|
|
24
|
+
syndicated: {
|
|
25
|
+
text: "posts.status.syndicated",
|
|
26
|
+
},
|
|
27
|
+
unlisted: {
|
|
28
|
+
color: "offset-purple",
|
|
29
|
+
icon: "unlisted",
|
|
30
|
+
text: "posts.status.unlisted",
|
|
31
|
+
},
|
|
32
|
+
};
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
import { sanitise, ISO_6709_RE } from "@indiekit/util";
|
|
4
|
+
import { mf2tojf2 } from "@paulrobertlloyd/mf2tojf2";
|
|
5
|
+
import formatcoords from "formatcoords";
|
|
6
|
+
|
|
7
|
+
import { endpoint } from "./endpoint.js";
|
|
8
|
+
import { statusTypes } from "./status-types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get channel `items` for checkboxes component
|
|
12
|
+
* @param {object} publication - Publication configuration
|
|
13
|
+
* @returns {object} Items for checkboxes component
|
|
14
|
+
*/
|
|
15
|
+
export const getChannelItems = (publication) => {
|
|
16
|
+
return Object.entries(publication.channels).map(([uid, channel]) => ({
|
|
17
|
+
label: channel.name,
|
|
18
|
+
value: uid,
|
|
19
|
+
}));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get geographic coordinates property
|
|
24
|
+
* @param {string} geo - Latitude and longitude, comma separated
|
|
25
|
+
* @returns {object} JF2 geo location property
|
|
26
|
+
*/
|
|
27
|
+
export const getGeoProperty = (geo) => {
|
|
28
|
+
const { latitude, longitude } = geo.match(ISO_6709_RE).groups;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
type: "geo",
|
|
32
|
+
name: formatcoords(geo).format({
|
|
33
|
+
decimalPlaces: 2,
|
|
34
|
+
}),
|
|
35
|
+
latitude: Number(latitude),
|
|
36
|
+
longitude: Number(longitude),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get comma separated geographic coordinates
|
|
42
|
+
* @param {object} location - JF2 location property
|
|
43
|
+
* @returns {string|undefined} Latitude and longitude, comma separated
|
|
44
|
+
*/
|
|
45
|
+
export const getGeoValue = (location) => {
|
|
46
|
+
if (location && location.geo) {
|
|
47
|
+
return [location.geo.latitude, location.geo.longitude].toString();
|
|
48
|
+
} else if (location && location.type === "geo") {
|
|
49
|
+
return [location.latitude, location.longitude].toString();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get location property
|
|
55
|
+
* @param {object} values - Latitude and longitude, comma separated
|
|
56
|
+
* @returns {object} JF2 location property
|
|
57
|
+
*/
|
|
58
|
+
export const getLocationProperty = (values) => {
|
|
59
|
+
const { geo, location } = values;
|
|
60
|
+
|
|
61
|
+
const hasGeo = geo && geo.length > 0;
|
|
62
|
+
const hasLocation = location && Object.entries(sanitise(location)).length > 0;
|
|
63
|
+
|
|
64
|
+
// Determine Microformat type
|
|
65
|
+
if (hasLocation && location.name) {
|
|
66
|
+
location.type = "card";
|
|
67
|
+
} else if (hasLocation && !hasGeo) {
|
|
68
|
+
location.type = "adr";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add (or use) any provided geo location properties
|
|
72
|
+
if (hasLocation && hasGeo) {
|
|
73
|
+
location.geo = getGeoProperty(geo);
|
|
74
|
+
} else if (hasGeo) {
|
|
75
|
+
return getGeoProperty(geo);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return sanitise(location);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get photo URL
|
|
83
|
+
* @param {object} publication - Publication configuration
|
|
84
|
+
* @param {object} properties - JF2 properties
|
|
85
|
+
* @returns {object|boolean} Photo object, with URL
|
|
86
|
+
*/
|
|
87
|
+
export const getPhotoUrl = (publication, properties) => {
|
|
88
|
+
const photo = Array.isArray(properties.photo)
|
|
89
|
+
? properties.photo[0]
|
|
90
|
+
: properties.photo;
|
|
91
|
+
|
|
92
|
+
if (!photo) {
|
|
93
|
+
return false;
|
|
94
|
+
} else if (URL.canParse(photo.url)) {
|
|
95
|
+
return photo;
|
|
96
|
+
} else {
|
|
97
|
+
return {
|
|
98
|
+
url: new URL(photo.url, publication.me).href,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get post status badges
|
|
105
|
+
* @param {object} post - Post
|
|
106
|
+
* @param {import("express").Response} response - Response
|
|
107
|
+
* @returns {Array} Badges
|
|
108
|
+
*/
|
|
109
|
+
export const getPostStatusBadges = (post, response) => {
|
|
110
|
+
const badges = [];
|
|
111
|
+
|
|
112
|
+
if (post["post-status"]) {
|
|
113
|
+
const statusType = post["post-status"];
|
|
114
|
+
badges.push({
|
|
115
|
+
color: statusTypes[statusType].color,
|
|
116
|
+
size: "small",
|
|
117
|
+
text: response.locals.__(statusTypes[statusType].text),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (post.deleted) {
|
|
122
|
+
badges.push({
|
|
123
|
+
color: statusTypes.deleted.color,
|
|
124
|
+
size: "small",
|
|
125
|
+
text: response.locals.__(statusTypes.deleted.text),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return badges;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get post name, falling back to post type name
|
|
134
|
+
* @param {object} publication - Publication configuration
|
|
135
|
+
* @param {object} properties - JF2 properties
|
|
136
|
+
* @returns {string} Post name or post type name
|
|
137
|
+
*/
|
|
138
|
+
export const getPostName = (publication, properties) => {
|
|
139
|
+
if (properties.name) {
|
|
140
|
+
return properties.name;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const type = properties["post-type"];
|
|
144
|
+
const { name } = publication.postTypes[type];
|
|
145
|
+
|
|
146
|
+
return name;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Query Micropub endpoint for post data
|
|
151
|
+
* @param {string} uid - Item UID
|
|
152
|
+
* @param {string} micropubEndpoint - Micropub endpoint
|
|
153
|
+
* @param {string} accessToken - Access token
|
|
154
|
+
* @returns {Promise<object>} JF2 properties
|
|
155
|
+
*/
|
|
156
|
+
export const getPostProperties = async (uid, micropubEndpoint, accessToken) => {
|
|
157
|
+
const micropubUrl = new URL(micropubEndpoint);
|
|
158
|
+
micropubUrl.searchParams.append("q", "source");
|
|
159
|
+
|
|
160
|
+
const micropubResponse = await endpoint.get(micropubUrl.href, accessToken);
|
|
161
|
+
|
|
162
|
+
if (micropubResponse?.items?.length > 0) {
|
|
163
|
+
const jf2 = mf2tojf2(micropubResponse);
|
|
164
|
+
const items = jf2.children || [jf2];
|
|
165
|
+
return items.find((item) => item.uid === uid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return false;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get post URL from ID
|
|
173
|
+
* @param {string} id - ID
|
|
174
|
+
* @returns {string} Post URL
|
|
175
|
+
*/
|
|
176
|
+
export const getPostUrl = (id) => {
|
|
177
|
+
const url = Buffer.from(id, "base64url").toString("utf8");
|
|
178
|
+
return new URL(url).href;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get syndication target `items` for checkboxes component
|
|
183
|
+
* @param {object} publication - Publication configuration
|
|
184
|
+
* @param {boolean} [checkTargets] - Select ’checked’ targets
|
|
185
|
+
* @returns {object} Items for checkboxes component
|
|
186
|
+
*/
|
|
187
|
+
export const getSyndicateToItems = (publication, checkTargets = false) => {
|
|
188
|
+
return publication.syndicationTargets.map((target) => ({
|
|
189
|
+
label: target.info.service.name,
|
|
190
|
+
...(target?.info?.error
|
|
191
|
+
? {
|
|
192
|
+
disabled: true,
|
|
193
|
+
hint: target?.info?.error || false,
|
|
194
|
+
}
|
|
195
|
+
: {
|
|
196
|
+
hint: target?.info.uid,
|
|
197
|
+
value: target?.info.uid,
|
|
198
|
+
...(checkTargets && { checked: target.options.checked }),
|
|
199
|
+
}),
|
|
200
|
+
}));
|
|
201
|
+
};
|
package/locales/de.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"posts": {
|
|
3
|
+
"create": {
|
|
4
|
+
"action": "Neuer Beitrag",
|
|
5
|
+
"title": "Einen neuen %s-Beitrag erstellen"
|
|
6
|
+
},
|
|
7
|
+
"delete": {
|
|
8
|
+
"action": "Beitrag löschen",
|
|
9
|
+
"cancel": "Nein — zurück zur Post",
|
|
10
|
+
"submit": "Ich bin mir sicher — lösche diesen Beitrag",
|
|
11
|
+
"title": "Möchtest du diesen Beitrag wirklich löschen?"
|
|
12
|
+
},
|
|
13
|
+
"error": {
|
|
14
|
+
"content": {
|
|
15
|
+
"empty": "Geben Sie einige Inhalte ein"
|
|
16
|
+
},
|
|
17
|
+
"featured-alt": {
|
|
18
|
+
"empty": "Geben Sie eine Beschreibung dieses Bildes ein"
|
|
19
|
+
},
|
|
20
|
+
"geo": {
|
|
21
|
+
"invalid": "Geben Sie gültige Koordinaten ein"
|
|
22
|
+
},
|
|
23
|
+
"media": {
|
|
24
|
+
"empty": "Geben Sie einen Dateipfad oder eine Webadresse wie %s ein"
|
|
25
|
+
},
|
|
26
|
+
"name": {
|
|
27
|
+
"empty": "Titel eingeben"
|
|
28
|
+
},
|
|
29
|
+
"type": {
|
|
30
|
+
"empty": "Wählen Sie einen Beitragstyp"
|
|
31
|
+
},
|
|
32
|
+
"url": {
|
|
33
|
+
"empty": "Geben Sie eine Webadresse wie %s"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"form": {
|
|
37
|
+
"advancedOptions": "Erweiterte Optionen",
|
|
38
|
+
"back": "Beitragstyp ändern",
|
|
39
|
+
"cancel": "Abbrechen",
|
|
40
|
+
"category": {
|
|
41
|
+
"hint": "Trennen Sie jede Kategorie durch ein Komma",
|
|
42
|
+
"label": "Kategorien",
|
|
43
|
+
"tag": "Kategorie"
|
|
44
|
+
},
|
|
45
|
+
"content": {
|
|
46
|
+
"label": "Inhalt"
|
|
47
|
+
},
|
|
48
|
+
"continue": "Fortsetzen",
|
|
49
|
+
"featured": {
|
|
50
|
+
"alt": "Barrierefreie Beschreibung",
|
|
51
|
+
"label": "Ausgewähltes Bild"
|
|
52
|
+
},
|
|
53
|
+
"geo": {
|
|
54
|
+
"hint": "Breitengrad und Längengrad, zum Beispiel %s",
|
|
55
|
+
"label": "Koordinaten des Standorts"
|
|
56
|
+
},
|
|
57
|
+
"location": {
|
|
58
|
+
"country-name": "Land",
|
|
59
|
+
"label": "Standort",
|
|
60
|
+
"locality": "Stadt oder Ort",
|
|
61
|
+
"name": "Veranstaltungsort",
|
|
62
|
+
"postal-code": "Postleitzahl",
|
|
63
|
+
"street-address": "Straße"
|
|
64
|
+
},
|
|
65
|
+
"media": {
|
|
66
|
+
"label": "Dateipfad oder URL"
|
|
67
|
+
},
|
|
68
|
+
"mp-channel": {
|
|
69
|
+
"label": "Kanal"
|
|
70
|
+
},
|
|
71
|
+
"mp-slug": {
|
|
72
|
+
"label": "Slug"
|
|
73
|
+
},
|
|
74
|
+
"mp-syndicate-to": {
|
|
75
|
+
"label": "Syndikat zu"
|
|
76
|
+
},
|
|
77
|
+
"name": {
|
|
78
|
+
"label": "Titel"
|
|
79
|
+
},
|
|
80
|
+
"publish": "Beitrag veröffentlichen",
|
|
81
|
+
"publishDraft": "Entwurf speichern",
|
|
82
|
+
"published": {
|
|
83
|
+
"label": "Veröffentlichungsdatum",
|
|
84
|
+
"now": "Jetzt",
|
|
85
|
+
"scheduled": "Bestimmtes Datum und Uhrzeit"
|
|
86
|
+
},
|
|
87
|
+
"summary": {
|
|
88
|
+
"label": "Übersicht"
|
|
89
|
+
},
|
|
90
|
+
"update": "Beitrag aktualisieren",
|
|
91
|
+
"updateDraft": "Entwurf aktualisieren",
|
|
92
|
+
"visibility": {
|
|
93
|
+
"label": "Sichtbarkeit"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"new": {
|
|
97
|
+
"title": "Welche Art von Beitrag möchtest du erstellen?"
|
|
98
|
+
},
|
|
99
|
+
"post": {
|
|
100
|
+
"properties": "Eigenschaften",
|
|
101
|
+
"syndicate": "Syndikatsposten"
|
|
102
|
+
},
|
|
103
|
+
"posts": {
|
|
104
|
+
"none": "Keine Beiträge",
|
|
105
|
+
"title": "Veröffentlichte Beiträge"
|
|
106
|
+
},
|
|
107
|
+
"status": {
|
|
108
|
+
"deleted": "Gelöscht",
|
|
109
|
+
"draft": "Entwurf",
|
|
110
|
+
"private": "Privat",
|
|
111
|
+
"public": "Öffentlich",
|
|
112
|
+
"published": "Veröffentlicht",
|
|
113
|
+
"syndicated": "Syndiziert",
|
|
114
|
+
"unlisted": "Nicht gelistete"
|
|
115
|
+
},
|
|
116
|
+
"title": "Beiträge",
|
|
117
|
+
"undelete": {
|
|
118
|
+
"action": "Beitrag wiederherstellen",
|
|
119
|
+
"submit": "Ich bin sicher – diesen Beitrag wiederherstellen",
|
|
120
|
+
"title": "Möchten Sie diesen Beitrag wirklich wiederherstellen?"
|
|
121
|
+
},
|
|
122
|
+
"update": {
|
|
123
|
+
"action": "Beitrag bearbeiten",
|
|
124
|
+
"title": "%s-Beitrag aktualisieren"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|