@fecommunity/reactpress-toolkit 4.0.0-beta.8 → 4.0.0
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 +19 -3
- package/dist/config/locales/en.json +1 -1
- package/dist/config/locales/zh.json +1 -1
- package/dist/plugin/extension/plugin-admin-locale.d.ts +5 -2
- package/dist/plugin/extension/plugin-admin-locale.io.js +13 -11
- package/dist/plugin/extension/plugin-admin-locale.js +5 -2
- package/dist/plugin/extension/plugin.d.ts +1 -1
- package/dist/plugin/extension/plugin.js +6 -15
- package/dist/theme/build/next-config.js +4 -1
- package/dist/ui/components/SiteCustomHtml.d.ts +17 -0
- package/dist/ui/components/SiteCustomHtml.js +29 -0
- package/dist/ui/components/custom-head-html.d.ts +8 -0
- package/dist/ui/components/custom-head-html.js +209 -0
- package/package.json +28 -6
package/README.md
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
# @fecommunity/reactpress-toolkit
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for the ReactPress publishing platform
|
|
3
|
+
> **Official TypeScript SDK for the ReactPress publishing platform.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@fecommunity/reactpress-toolkit/v/beta)
|
|
6
|
+
[](https://www.npmjs.com/package/@fecommunity/reactpress-toolkit)
|
|
7
|
+
[](https://www.npmjs.com/package/@fecommunity/reactpress-toolkit)
|
|
8
|
+
[](https://github.com/fecommunity/reactpress/blob/master/LICENSE)
|
|
9
|
+
|
|
10
|
+
The shared foundation for **ReactPress themes, plugins, Admin extensions, and custom frontends**. Auto-generated OpenAPI clients, Next.js theme SSR helpers, plugin admin hooks, and headless React UI — so you extend the platform instead of duplicating API or rendering logic.
|
|
11
|
+
|
|
12
|
+
| Use case | Start here |
|
|
13
|
+
| :------- | :--------- |
|
|
14
|
+
| **Next.js themes** | `./theme`, `./theme/next-config`, `./app` |
|
|
15
|
+
| **Plugins** | `./plugin`, `./plugin/admin`, `./plugin/client` |
|
|
16
|
+
| **API integration** | `./api`, `./api/instance`, `./types` |
|
|
17
|
+
| **Headless UI** | `./ui` — unstyled components and hooks |
|
|
18
|
+
|
|
19
|
+
[Documentation](https://docs.gaoredu.com/) · [ReactPress CLI](../cli/) · [GitHub](https://github.com/fecommunity/reactpress)
|
|
6
20
|
|
|
7
21
|
## Package exports (`package.json` → `exports`)
|
|
8
22
|
|
|
@@ -271,4 +285,6 @@ Output in `dist/`; `createApp.js` is emitted via `tsc` (`allowJs`) to `dist/them
|
|
|
271
285
|
|
|
272
286
|
## License
|
|
273
287
|
|
|
274
|
-
|
|
288
|
+
MIT © [FECommunity](https://github.com/fecommunity)
|
|
289
|
+
|
|
290
|
+
<p align="center"><sub>Part of <a href="https://github.com/fecommunity/reactpress">ReactPress</a> — the official SDK for themes, plugins, and custom frontends.</sub></p>
|
|
@@ -121,5 +121,5 @@
|
|
|
121
121
|
"navCategoryMenu": "Navigation categories",
|
|
122
122
|
"carouselPrev": "Previous slide, {current} of {total}",
|
|
123
123
|
"carouselNext": "Next slide, {current} of {total}",
|
|
124
|
-
"aboutUsFallback": "ReactPress —
|
|
124
|
+
"aboutUsFallback": "ReactPress — Publish with React. Ship like WordPress."
|
|
125
125
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { getNestedLocaleString, getNestedLocaleValue } from '../../utils/object';
|
|
2
|
-
/** Admin copy shipped with the plugin (`locales/{locale}.json`). */
|
|
2
|
+
/** Admin copy shipped with the plugin (`src/locales/{locale}.json`). */
|
|
3
3
|
export type PluginAdminLocaleMessages = Record<string, unknown>;
|
|
4
|
-
|
|
4
|
+
/** Primary locale directory inside a plugin package. */
|
|
5
|
+
export declare const PLUGIN_LOCALE_DIR = "src/locales";
|
|
6
|
+
/** Legacy root-level locale dir (pre–4.x layout); kept for older bundled plugins. */
|
|
7
|
+
export declare const PLUGIN_LOCALE_DIR_LEGACY = "locales";
|
|
5
8
|
export { getNestedLocaleString, getNestedLocaleValue };
|
|
6
9
|
type PluginSchemaProperty = {
|
|
7
10
|
type?: string;
|
|
@@ -37,18 +37,20 @@ exports.readPluginAdminLocaleFile = readPluginAdminLocaleFile;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const plugin_admin_locale_1 = require("./plugin-admin-locale");
|
|
40
|
+
const PLUGIN_LOCALE_SEARCH_DIRS = [plugin_admin_locale_1.PLUGIN_LOCALE_DIR, plugin_admin_locale_1.PLUGIN_LOCALE_DIR_LEGACY];
|
|
40
41
|
function readPluginAdminLocaleFile(pluginDir, locale) {
|
|
41
42
|
const safeLocale = /^[a-z]{2}(?:-[a-z]{2})?$/i.test(locale) ? locale : 'en';
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
? raw
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
for (const localeDir of PLUGIN_LOCALE_SEARCH_DIRS) {
|
|
44
|
+
const filePath = path.join(pluginDir, localeDir, `${safeLocale}.json`);
|
|
45
|
+
if (!fs.existsSync(filePath))
|
|
46
|
+
continue;
|
|
47
|
+
try {
|
|
48
|
+
const raw = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
49
|
+
return raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : null;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
53
54
|
}
|
|
55
|
+
return null;
|
|
54
56
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getNestedLocaleValue = exports.getNestedLocaleString = exports.PLUGIN_LOCALE_DIR = void 0;
|
|
3
|
+
exports.getNestedLocaleValue = exports.getNestedLocaleString = exports.PLUGIN_LOCALE_DIR_LEGACY = exports.PLUGIN_LOCALE_DIR = void 0;
|
|
4
4
|
exports.resolvePluginAdminLocaleText = resolvePluginAdminLocaleText;
|
|
5
5
|
exports.localizePluginSettingsSchemaWithLocale = localizePluginSettingsSchemaWithLocale;
|
|
6
6
|
const object_1 = require("../../utils/object");
|
|
7
7
|
Object.defineProperty(exports, "getNestedLocaleString", { enumerable: true, get: function () { return object_1.getNestedLocaleString; } });
|
|
8
8
|
Object.defineProperty(exports, "getNestedLocaleValue", { enumerable: true, get: function () { return object_1.getNestedLocaleValue; } });
|
|
9
|
-
|
|
9
|
+
/** Primary locale directory inside a plugin package. */
|
|
10
|
+
exports.PLUGIN_LOCALE_DIR = 'src/locales';
|
|
11
|
+
/** Legacy root-level locale dir (pre–4.x layout); kept for older bundled plugins. */
|
|
12
|
+
exports.PLUGIN_LOCALE_DIR_LEGACY = 'locales';
|
|
10
13
|
/** Resolve admin copy from plugin locale bundle; falls back to manifest text. */
|
|
11
14
|
function resolvePluginAdminLocaleText(messages, dotPath, fallback) {
|
|
12
15
|
const fromLocale = messages ? (0, object_1.getNestedLocaleString)(messages, dotPath) : undefined;
|
|
@@ -20,7 +20,7 @@ export interface PluginAdminSlotsManifest {
|
|
|
20
20
|
subscribe?: AdminSlotId[];
|
|
21
21
|
}
|
|
22
22
|
export interface PluginAdminManifest {
|
|
23
|
-
/** @deprecated Use convention `admin/index.ts`; declared slots drive loading. */
|
|
23
|
+
/** @deprecated Use convention `src/admin/index.ts`; declared slots drive loading. */
|
|
24
24
|
entry?: string;
|
|
25
25
|
slots?: PluginAdminSlotsManifest;
|
|
26
26
|
menu?: PluginAdminMenuManifest;
|
|
@@ -39,15 +39,12 @@ function parsePluginManifest(raw) {
|
|
|
39
39
|
hooks: serverRaw.hooks &&
|
|
40
40
|
typeof serverRaw.hooks === 'object'
|
|
41
41
|
? {
|
|
42
|
-
subscribe: Array.isArray(serverRaw.hooks
|
|
43
|
-
.subscribe)
|
|
42
|
+
subscribe: Array.isArray(serverRaw.hooks.subscribe)
|
|
44
43
|
? serverRaw.hooks
|
|
45
44
|
.subscribe.filter((h) => typeof h === 'string')
|
|
46
45
|
: undefined,
|
|
47
|
-
provide: Array.isArray(serverRaw.hooks
|
|
48
|
-
.provide)
|
|
49
|
-
? serverRaw.hooks
|
|
50
|
-
.provide.filter((h) => typeof h === 'string')
|
|
46
|
+
provide: Array.isArray(serverRaw.hooks.provide)
|
|
47
|
+
? serverRaw.hooks.provide.filter((h) => typeof h === 'string')
|
|
51
48
|
: undefined,
|
|
52
49
|
}
|
|
53
50
|
: undefined,
|
|
@@ -59,17 +56,13 @@ function parsePluginManifest(raw) {
|
|
|
59
56
|
? (() => {
|
|
60
57
|
const raw = adminRaw;
|
|
61
58
|
const slotsRaw = raw.slots;
|
|
62
|
-
const subscribe = slotsRaw &&
|
|
63
|
-
typeof slotsRaw === 'object' &&
|
|
64
|
-
Array.isArray(slotsRaw.subscribe)
|
|
59
|
+
const subscribe = slotsRaw && typeof slotsRaw === 'object' && Array.isArray(slotsRaw.subscribe)
|
|
65
60
|
? slotsRaw.subscribe.filter((slot) => typeof slot === 'string' && (0, slots_1.isAdminSlotId)(slot))
|
|
66
61
|
: undefined;
|
|
67
62
|
return {
|
|
68
63
|
entry: typeof raw.entry === 'string' ? raw.entry : undefined,
|
|
69
64
|
slots: subscribe?.length ? { subscribe } : undefined,
|
|
70
|
-
menu: raw.menu && typeof raw.menu === 'object'
|
|
71
|
-
? raw.menu
|
|
72
|
-
: undefined,
|
|
65
|
+
menu: raw.menu && typeof raw.menu === 'object' ? raw.menu : undefined,
|
|
73
66
|
};
|
|
74
67
|
})()
|
|
75
68
|
: undefined;
|
|
@@ -83,9 +76,7 @@ function parsePluginManifest(raw) {
|
|
|
83
76
|
}
|
|
84
77
|
: undefined;
|
|
85
78
|
const capsRaw = o.capabilities;
|
|
86
|
-
const capabilities = capsRaw && typeof capsRaw === 'object'
|
|
87
|
-
? capsRaw
|
|
88
|
-
: undefined;
|
|
79
|
+
const capabilities = capsRaw && typeof capsRaw === 'object' ? capsRaw : undefined;
|
|
89
80
|
return {
|
|
90
81
|
id: o.id,
|
|
91
82
|
name: o.name,
|
|
@@ -41,9 +41,12 @@ function applyThemeWebpackResolve(config, appDir) {
|
|
|
41
41
|
* Pass overrides to extend (your `webpack` runs after the supports-color alias).
|
|
42
42
|
*/
|
|
43
43
|
function createReactPressNextConfig(overrides = {}) {
|
|
44
|
-
const { webpack: userWebpack, eslint, transpilePackages, ...rest } = overrides;
|
|
44
|
+
const { webpack: userWebpack, eslint, transpilePackages, compress, ...rest } = overrides;
|
|
45
45
|
return {
|
|
46
46
|
reactStrictMode: true,
|
|
47
|
+
// Themes rewrite `/api` to Nest. Next's compress middleware decompresses upstream
|
|
48
|
+
// bodies but can still emit `Content-Encoding: br`, breaking admin fetch (ERR_CONTENT_DECODING_FAILED).
|
|
49
|
+
compress: compress ?? false,
|
|
47
50
|
transpilePackages: ['@fecommunity/reactpress-toolkit', ...(transpilePackages ?? [])],
|
|
48
51
|
eslint: {
|
|
49
52
|
ignoreDuringBuilds: true,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SiteCustomHtmlProps {
|
|
3
|
+
children?: React.ReactNode;
|
|
4
|
+
/**
|
|
5
|
+
* When true (default), wrap nodes in `next/head` for Pages Router SSR.
|
|
6
|
+
* App Router themes should pass `html` into layout `<head>` via `renderCustomHeadHtml` instead.
|
|
7
|
+
*/
|
|
8
|
+
useNextHead?: boolean;
|
|
9
|
+
/** Optional HTML override (skips reading site setting). */
|
|
10
|
+
html?: string;
|
|
11
|
+
}
|
|
12
|
+
export { renderCustomHeadHtml, resolveCustomHeadHtml } from './custom-head-html';
|
|
13
|
+
/**
|
|
14
|
+
* SSR-friendly custom head HTML from Admin → Settings → SEO (`customHeadHtml`).
|
|
15
|
+
* Pages Router: renders through `next/head`. App Router: prefer `renderCustomHeadHtml` in root layout.
|
|
16
|
+
*/
|
|
17
|
+
export declare function SiteCustomHtml({ children, useNextHead, html, }: SiteCustomHtmlProps): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveCustomHeadHtml = exports.renderCustomHeadHtml = void 0;
|
|
7
|
+
exports.SiteCustomHtml = SiteCustomHtml;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
const head_1 = __importDefault(require("next/head"));
|
|
10
|
+
const SiteCatalogContext_1 = require("../context/SiteCatalogContext");
|
|
11
|
+
const custom_head_html_1 = require("./custom-head-html");
|
|
12
|
+
var custom_head_html_2 = require("./custom-head-html");
|
|
13
|
+
Object.defineProperty(exports, "renderCustomHeadHtml", { enumerable: true, get: function () { return custom_head_html_2.renderCustomHeadHtml; } });
|
|
14
|
+
Object.defineProperty(exports, "resolveCustomHeadHtml", { enumerable: true, get: function () { return custom_head_html_2.resolveCustomHeadHtml; } });
|
|
15
|
+
/**
|
|
16
|
+
* SSR-friendly custom head HTML from Admin → Settings → SEO (`customHeadHtml`).
|
|
17
|
+
* Pages Router: renders through `next/head`. App Router: prefer `renderCustomHeadHtml` in root layout.
|
|
18
|
+
*/
|
|
19
|
+
function SiteCustomHtml({ children = null, useNextHead = true, html, }) {
|
|
20
|
+
const setting = (0, SiteCatalogContext_1.useSiteSetting)();
|
|
21
|
+
const source = html ?? (0, custom_head_html_1.resolveCustomHeadHtml)(setting);
|
|
22
|
+
const nodes = (0, custom_head_html_1.renderCustomHeadHtml)(source);
|
|
23
|
+
if (nodes.length === 0)
|
|
24
|
+
return children;
|
|
25
|
+
if (useNextHead) {
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(head_1.default, { children: nodes }), children] }));
|
|
27
|
+
}
|
|
28
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [nodes, children] }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Parse admin custom head HTML into React nodes for SSR (App Router `<head>` or `next/head`).
|
|
4
|
+
* Supports common head tags: script / meta / link / style / noscript.
|
|
5
|
+
*/
|
|
6
|
+
export declare function renderCustomHeadHtml(html: string): React.ReactNode[];
|
|
7
|
+
/** Resolve custom head HTML from a setting row (flat field or `globalSetting` JSON). */
|
|
8
|
+
export declare function resolveCustomHeadHtml(setting: Record<string, unknown> | null | undefined): string;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.renderCustomHeadHtml = renderCustomHeadHtml;
|
|
7
|
+
exports.resolveCustomHeadHtml = resolveCustomHeadHtml;
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const VOID_TAGS = new Set([
|
|
10
|
+
'area',
|
|
11
|
+
'base',
|
|
12
|
+
'br',
|
|
13
|
+
'col',
|
|
14
|
+
'embed',
|
|
15
|
+
'hr',
|
|
16
|
+
'img',
|
|
17
|
+
'input',
|
|
18
|
+
'link',
|
|
19
|
+
'meta',
|
|
20
|
+
'param',
|
|
21
|
+
'source',
|
|
22
|
+
'track',
|
|
23
|
+
'wbr',
|
|
24
|
+
]);
|
|
25
|
+
const BOOL_ATTRS = new Set([
|
|
26
|
+
'async',
|
|
27
|
+
'defer',
|
|
28
|
+
'nomodule',
|
|
29
|
+
'checked',
|
|
30
|
+
'disabled',
|
|
31
|
+
'readonly',
|
|
32
|
+
'required',
|
|
33
|
+
'selected',
|
|
34
|
+
'multiple',
|
|
35
|
+
'muted',
|
|
36
|
+
'playsinline',
|
|
37
|
+
'autofocus',
|
|
38
|
+
'autoplay',
|
|
39
|
+
'controls',
|
|
40
|
+
'loop',
|
|
41
|
+
'open',
|
|
42
|
+
]);
|
|
43
|
+
const ATTR_NAME_MAP = {
|
|
44
|
+
class: 'className',
|
|
45
|
+
charset: 'charSet',
|
|
46
|
+
charSet: 'charSet',
|
|
47
|
+
crossorigin: 'crossOrigin',
|
|
48
|
+
crossOrigin: 'crossOrigin',
|
|
49
|
+
httpequiv: 'httpEquiv',
|
|
50
|
+
'http-equiv': 'httpEquiv',
|
|
51
|
+
httpEquiv: 'httpEquiv',
|
|
52
|
+
for: 'htmlFor',
|
|
53
|
+
tabindex: 'tabIndex',
|
|
54
|
+
readonly: 'readOnly',
|
|
55
|
+
maxlength: 'maxLength',
|
|
56
|
+
minlength: 'minLength',
|
|
57
|
+
allowfullscreen: 'allowFullScreen',
|
|
58
|
+
nomodule: 'noModule',
|
|
59
|
+
playsinline: 'playsInline',
|
|
60
|
+
};
|
|
61
|
+
function toReactAttrName(name) {
|
|
62
|
+
if (ATTR_NAME_MAP[name])
|
|
63
|
+
return ATTR_NAME_MAP[name];
|
|
64
|
+
if (ATTR_NAME_MAP[name.toLowerCase()])
|
|
65
|
+
return ATTR_NAME_MAP[name.toLowerCase()];
|
|
66
|
+
if (name.startsWith('data-') || name.startsWith('aria-'))
|
|
67
|
+
return name;
|
|
68
|
+
return name;
|
|
69
|
+
}
|
|
70
|
+
function parseAttributes(attrSource) {
|
|
71
|
+
const attrs = {};
|
|
72
|
+
const re = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
73
|
+
let match;
|
|
74
|
+
while ((match = re.exec(attrSource))) {
|
|
75
|
+
const rawName = match[1];
|
|
76
|
+
if (!rawName || rawName === '/')
|
|
77
|
+
continue;
|
|
78
|
+
const reactName = toReactAttrName(rawName);
|
|
79
|
+
const rawValue = match[2] ?? match[3] ?? match[4];
|
|
80
|
+
if (rawValue === undefined) {
|
|
81
|
+
attrs[reactName] = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (BOOL_ATTRS.has(rawName.toLowerCase()) || BOOL_ATTRS.has(reactName.toLowerCase())) {
|
|
85
|
+
attrs[reactName] = rawValue === '' || rawValue.toLowerCase() === reactName.toLowerCase() || rawValue === 'true';
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
attrs[reactName] = rawValue;
|
|
89
|
+
}
|
|
90
|
+
return attrs;
|
|
91
|
+
}
|
|
92
|
+
function findClosingTag(html, tagName, fromIndex) {
|
|
93
|
+
const openRe = new RegExp(`<${tagName}\\b`, 'gi');
|
|
94
|
+
const closeRe = new RegExp(`</${tagName}\\s*>`, 'gi');
|
|
95
|
+
let depth = 1;
|
|
96
|
+
let i = fromIndex;
|
|
97
|
+
while (i < html.length && depth > 0) {
|
|
98
|
+
openRe.lastIndex = i;
|
|
99
|
+
closeRe.lastIndex = i;
|
|
100
|
+
const openMatch = openRe.exec(html);
|
|
101
|
+
const closeMatch = closeRe.exec(html);
|
|
102
|
+
if (!closeMatch)
|
|
103
|
+
return -1;
|
|
104
|
+
if (openMatch && openMatch.index < closeMatch.index) {
|
|
105
|
+
depth += 1;
|
|
106
|
+
i = openMatch.index + openMatch[0].length;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
depth -= 1;
|
|
110
|
+
if (depth === 0)
|
|
111
|
+
return closeMatch.index;
|
|
112
|
+
i = closeMatch.index + closeMatch[0].length;
|
|
113
|
+
}
|
|
114
|
+
return -1;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Parse admin custom head HTML into React nodes for SSR (App Router `<head>` or `next/head`).
|
|
118
|
+
* Supports common head tags: script / meta / link / style / noscript.
|
|
119
|
+
*/
|
|
120
|
+
function renderCustomHeadHtml(html) {
|
|
121
|
+
const source = String(html || '').trim();
|
|
122
|
+
if (!source)
|
|
123
|
+
return [];
|
|
124
|
+
const nodes = [];
|
|
125
|
+
let i = 0;
|
|
126
|
+
let key = 0;
|
|
127
|
+
while (i < source.length) {
|
|
128
|
+
const nextTag = source.indexOf('<', i);
|
|
129
|
+
if (nextTag === -1)
|
|
130
|
+
break;
|
|
131
|
+
if (nextTag > i && source.slice(i, nextTag).trim()) {
|
|
132
|
+
// Ignore loose text nodes in <head>
|
|
133
|
+
i = nextTag;
|
|
134
|
+
}
|
|
135
|
+
if (source.startsWith('<!--', nextTag)) {
|
|
136
|
+
const end = source.indexOf('-->', nextTag + 4);
|
|
137
|
+
i = end === -1 ? source.length : end + 3;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const tagMatch = source
|
|
141
|
+
.slice(nextTag)
|
|
142
|
+
.match(/^<\/?([a-zA-Z][\w:-]*)((?:\s[^>]*)?)(\/?)>/);
|
|
143
|
+
if (!tagMatch) {
|
|
144
|
+
i = nextTag + 1;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const full = tagMatch[0];
|
|
148
|
+
const tagName = tagMatch[1].toLowerCase();
|
|
149
|
+
const attrSource = tagMatch[2] || '';
|
|
150
|
+
const selfClosing = Boolean(tagMatch[3]) || VOID_TAGS.has(tagName);
|
|
151
|
+
const openEnd = nextTag + full.length;
|
|
152
|
+
if (full.startsWith('</')) {
|
|
153
|
+
i = openEnd;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const attrs = parseAttributes(attrSource);
|
|
157
|
+
attrs['data-reactpress-custom-html'] = 'head';
|
|
158
|
+
if (selfClosing || VOID_TAGS.has(tagName)) {
|
|
159
|
+
nodes.push(react_1.default.createElement(tagName, { ...attrs, key: `rp-custom-head-${key++}` }));
|
|
160
|
+
i = openEnd;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const closeIndex = findClosingTag(source, tagName, openEnd);
|
|
164
|
+
if (closeIndex === -1) {
|
|
165
|
+
nodes.push(react_1.default.createElement(tagName, { ...attrs, key: `rp-custom-head-${key++}` }));
|
|
166
|
+
i = openEnd;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const inner = source.slice(openEnd, closeIndex);
|
|
170
|
+
const closeMatch = source.slice(closeIndex).match(new RegExp(`^</${tagName}\\s*>`, 'i'));
|
|
171
|
+
i = closeIndex + (closeMatch ? closeMatch[0].length : 0);
|
|
172
|
+
if (tagName === 'script' || tagName === 'style') {
|
|
173
|
+
nodes.push(react_1.default.createElement(tagName, {
|
|
174
|
+
...attrs,
|
|
175
|
+
key: `rp-custom-head-${key++}`,
|
|
176
|
+
dangerouslySetInnerHTML: { __html: inner },
|
|
177
|
+
}));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
nodes.push(react_1.default.createElement(tagName, { ...attrs, key: `rp-custom-head-${key++}` }, inner || null));
|
|
181
|
+
}
|
|
182
|
+
return nodes;
|
|
183
|
+
}
|
|
184
|
+
/** Resolve custom head HTML from a setting row (flat field or `globalSetting` JSON). */
|
|
185
|
+
function resolveCustomHeadHtml(setting) {
|
|
186
|
+
if (!setting)
|
|
187
|
+
return '';
|
|
188
|
+
const direct = setting.customHeadHtml;
|
|
189
|
+
if (typeof direct === 'string' && direct.trim())
|
|
190
|
+
return direct;
|
|
191
|
+
const raw = setting.globalSetting;
|
|
192
|
+
let gs = null;
|
|
193
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
194
|
+
gs = raw;
|
|
195
|
+
}
|
|
196
|
+
else if (typeof raw === 'string' && raw.trim()) {
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(raw);
|
|
199
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
200
|
+
gs = parsed;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
gs = null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const nested = gs?.customHeadHtml;
|
|
208
|
+
return typeof nested === 'string' ? nested : '';
|
|
209
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fecommunity/reactpress-toolkit",
|
|
3
|
-
"version": "4.0.0
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"types": "./dist/theme/node.d.ts",
|
|
45
45
|
"default": "./dist/theme/node.js"
|
|
46
46
|
},
|
|
47
|
+
"./theme/extension": {
|
|
48
|
+
"types": "./dist/theme/extension/index.d.ts",
|
|
49
|
+
"default": "./dist/theme/extension/index.js"
|
|
50
|
+
},
|
|
47
51
|
"./theme/providers": {
|
|
48
52
|
"types": "./dist/theme/providers/index.d.ts",
|
|
49
53
|
"default": "./dist/theme/providers/index.js"
|
|
@@ -130,17 +134,35 @@
|
|
|
130
134
|
}
|
|
131
135
|
},
|
|
132
136
|
"files": [
|
|
133
|
-
"dist"
|
|
137
|
+
"dist",
|
|
138
|
+
"README.md",
|
|
139
|
+
"LICENSE"
|
|
134
140
|
],
|
|
135
141
|
"keywords": [
|
|
136
142
|
"reactpress",
|
|
143
|
+
"typescript",
|
|
144
|
+
"sdk",
|
|
145
|
+
"headless-cms",
|
|
137
146
|
"cms",
|
|
138
|
-
"
|
|
147
|
+
"blog",
|
|
148
|
+
"nextjs",
|
|
149
|
+
"openapi",
|
|
150
|
+
"api-client",
|
|
151
|
+
"ssr",
|
|
139
152
|
"theme",
|
|
140
|
-
"
|
|
153
|
+
"plugin",
|
|
154
|
+
"react",
|
|
155
|
+
"publishing-platform"
|
|
141
156
|
],
|
|
142
157
|
"author": "fecommunity",
|
|
143
|
-
"license": "
|
|
158
|
+
"license": "MIT",
|
|
159
|
+
"engines": {
|
|
160
|
+
"node": ">=18"
|
|
161
|
+
},
|
|
162
|
+
"homepage": "https://docs.gaoredu.com/",
|
|
163
|
+
"bugs": {
|
|
164
|
+
"url": "https://github.com/fecommunity/reactpress/issues"
|
|
165
|
+
},
|
|
144
166
|
"repository": {
|
|
145
167
|
"type": "git",
|
|
146
168
|
"url": "git+https://github.com/fecommunity/reactpress.git",
|
|
@@ -150,7 +172,7 @@
|
|
|
150
172
|
"access": "public",
|
|
151
173
|
"registry": "https://registry.npmjs.org/"
|
|
152
174
|
},
|
|
153
|
-
"description": "ReactPress TypeScript SDK —
|
|
175
|
+
"description": "Official ReactPress TypeScript SDK — OpenAPI REST clients, Next.js theme SSR, plugin hooks, and headless UI for the publishing platform",
|
|
154
176
|
"dependencies": {
|
|
155
177
|
"ajv": "^8.17.1",
|
|
156
178
|
"axios": "^1.12.2",
|