@blocklet/pages-kit-block-studio 0.1.4 → 0.1.7

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.
@@ -275,12 +275,10 @@ function getCustomComponentPropertyType(type) {
275
275
  }
276
276
  const ALLOWED_PROPERTY_TYPES = ['string', 'multiline', 'url', 'json', 'yaml'];
277
277
  function generatePageDataTypes(state) {
278
- const pageTypes = Object.values(state.pages)
278
+ const pageTypeDefinitions = Object.values(state.pages)
279
279
  .map((page) => {
280
- // check if the page is a template page
281
280
  if (!page.isTemplate)
282
281
  return null;
283
- // 将 slug 转换为有效的 TypeScript 类型名
284
282
  const typeName = `Page${page.slug
285
283
  .replace(/^\//, '')
286
284
  .split('/')
@@ -331,8 +329,7 @@ ${properties}
331
329
  })
332
330
  .filter(Boolean)
333
331
  .join('\n');
334
- return `
335
- export interface ${typeName} {
332
+ const typeDefinition = `interface ${typeName} {
336
333
  title?: string;
337
334
  image?: string;
338
335
  description?: string;
@@ -340,29 +337,28 @@ export interface ${typeName} {
340
337
  ${sectionTypes}
341
338
  };
342
339
  }`;
340
+ return {
341
+ typeName,
342
+ typeDefinition,
343
+ };
343
344
  })
344
- .filter(Boolean)
345
- .join('\n');
346
- // 生成 PageData 类型的联合类型
347
- const pageUnionTypes = Object.values(state.pages)
348
- .map((page) => {
349
- if (!page.isTemplate)
350
- return null;
351
- const typeName = `Page${page.slug
352
- .replace(/^\//, '')
353
- .split('/')
354
- .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
355
- .join('')
356
- .replace(/[^a-zA-Z0-9]/g, '')}Data`;
357
- return typeName;
358
- })
359
- .filter(Boolean)
360
- .join(' | ');
345
+ .filter(Boolean);
346
+ // Generate type definitions string
347
+ const typeDefinitions = pageTypeDefinitions.map((def) => `export ${def === null || def === void 0 ? void 0 : def.typeDefinition}`).join('\n\n');
348
+ // Generate string literals for each type
349
+ const typeStrings = pageTypeDefinitions
350
+ .map((def) => `export const ${def === null || def === void 0 ? void 0 : def.typeName}String = \`${def === null || def === void 0 ? void 0 : def.typeDefinition}\`;`)
351
+ .join('\n\n');
352
+ // Generate union type
353
+ const pageUnionTypes = pageTypeDefinitions.map((def) => def === null || def === void 0 ? void 0 : def.typeName).join(' | ');
361
354
  return `
362
- // 页面数据类型定义
363
- ${pageTypes}
355
+ // Page data type definitions
356
+ ${typeDefinitions}
357
+
358
+ // String versions of type definitions
359
+ ${typeStrings}
364
360
 
365
- // 所有页面数据类型的联合类型
361
+ // Union type of all page data types
366
362
  export type PageDataUnion = ${pageUnionTypes || 'never'};`;
367
363
  }
368
364
  exports.default = generateWrapperCode;
@@ -32,6 +32,9 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
+ };
35
38
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
39
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
40
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -45,7 +48,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
45
48
  return (mod && mod.__esModule) ? mod : { "default": mod };
46
49
  };
47
50
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.getBlockCode = exports.isPagesKitBlockStudio = exports.getPreviewImageRelativePath = exports.downloadAsset = exports.isMetadataFile = exports.isDev = exports.isPathSafe = exports.NANOID_LENGTH = exports.PREVIEW_IMAGE_DIR = exports.METADATA_FILE_NAME = exports.libDir = exports.logger = void 0;
51
+ exports.safeParse = exports.getBlockCode = exports.isPagesKitBlockStudio = exports.getPreviewImageRelativePath = exports.downloadAsset = exports.isMetadataFile = exports.isDev = exports.isPathSafe = exports.logger = void 0;
49
52
  exports.setBlockEntryFilesPattern = setBlockEntryFilesPattern;
50
53
  exports.getBlockEntryFilesPattern = getBlockEntryFilesPattern;
51
54
  exports.findComponentFiles = findComponentFiles;
@@ -64,14 +67,10 @@ const promises_2 = require("stream/promises");
64
67
  const ufo_1 = require("ufo");
65
68
  const yaml = __importStar(require("yaml"));
66
69
  const constants_1 = require("../constants");
70
+ __exportStar(require("../constants"), exports);
67
71
  exports.logger = console;
68
- exports.libDir = 'lib';
69
- exports.METADATA_FILE_NAME = '@metadata.json';
70
- exports.PREVIEW_IMAGE_DIR = '@preview-images';
71
- exports.NANOID_LENGTH = 16;
72
- const DEFAULT_BLOCK_ENTRY_FILES_PATTERN = 'src/**/index.{ts,tsx,html}';
73
72
  if (!process.env.BLOCK_ENTRY_FILES_PATTERN) {
74
- process.env.BLOCK_ENTRY_FILES_PATTERN = DEFAULT_BLOCK_ENTRY_FILES_PATTERN;
73
+ process.env.BLOCK_ENTRY_FILES_PATTERN = constants_1.DEFAULT_BLOCK_ENTRY_FILES_PATTERN;
75
74
  }
76
75
  function normalizePattern(pattern) {
77
76
  return pattern.replace(/^[/\\]+/, ''); // Remove leading slashes
@@ -80,7 +79,7 @@ function setBlockEntryFilesPattern(pattern) {
80
79
  process.env.BLOCK_ENTRY_FILES_PATTERN = normalizePattern(pattern);
81
80
  }
82
81
  function getBlockEntryFilesPattern() {
83
- return process.env.BLOCK_ENTRY_FILES_PATTERN || DEFAULT_BLOCK_ENTRY_FILES_PATTERN;
82
+ return process.env.BLOCK_ENTRY_FILES_PATTERN || constants_1.DEFAULT_BLOCK_ENTRY_FILES_PATTERN;
84
83
  }
85
84
  function findComponentFiles(options = {}) {
86
85
  const { cwd = process.cwd(), filter } = options;
@@ -124,7 +123,7 @@ const isPathSafe = (filePath) => {
124
123
  exports.isPathSafe = isPathSafe;
125
124
  exports.isDev = process.env.BLOCKLET_MODE === 'development';
126
125
  const isMetadataFile = (filePath) => {
127
- return filePath.endsWith(exports.METADATA_FILE_NAME);
126
+ return filePath.endsWith(constants_1.METADATA_FILE_NAME);
128
127
  };
129
128
  exports.isMetadataFile = isMetadataFile;
130
129
  const downloadAsset = (_a) => __awaiter(void 0, [_a], void 0, function* ({ asset, savePath, componentDid, }) {
@@ -155,7 +154,7 @@ const downloadAsset = (_a) => __awaiter(void 0, [_a], void 0, function* ({ asset
155
154
  });
156
155
  exports.downloadAsset = downloadAsset;
157
156
  const getPreviewImageRelativePath = (name) => {
158
- return path_1.default.join(exports.PREVIEW_IMAGE_DIR, name);
157
+ return path_1.default.join(constants_1.PREVIEW_IMAGE_DIR, name);
159
158
  };
160
159
  exports.getPreviewImageRelativePath = getPreviewImageRelativePath;
161
160
  function initializeMetadata(tempFilePath) {
@@ -164,7 +163,7 @@ function initializeMetadata(tempFilePath) {
164
163
  throw new Error('File path is required');
165
164
  }
166
165
  // file path is metadata file
167
- const filePath = (0, exports.isMetadataFile)(tempFilePath) ? tempFilePath : path_1.default.join((0, path_1.dirname)(tempFilePath), exports.METADATA_FILE_NAME);
166
+ const filePath = (0, exports.isMetadataFile)(tempFilePath) ? tempFilePath : path_1.default.join((0, path_1.dirname)(tempFilePath), constants_1.METADATA_FILE_NAME);
168
167
  if (!filePath) {
169
168
  throw new Error('File path is required');
170
169
  }
@@ -182,7 +181,7 @@ function initializeMetadata(tempFilePath) {
182
181
  }
183
182
  // Add id if not exists
184
183
  if (!metadata.id) {
185
- metadata.id = (0, nanoid_1.nanoid)(exports.NANOID_LENGTH);
184
+ metadata.id = (0, nanoid_1.nanoid)(constants_1.NANOID_LENGTH);
186
185
  }
187
186
  // Add createdAt if not exists
188
187
  if (!metadata.createdAt) {
@@ -227,3 +226,12 @@ const getBlockCode = (filePath) => {
227
226
  return fs_1.default.readFileSync(codeFile, 'utf8');
228
227
  };
229
228
  exports.getBlockCode = getBlockCode;
229
+ const safeParse = (text) => {
230
+ try {
231
+ return JSON.parse(text);
232
+ }
233
+ catch (_a) {
234
+ return null;
235
+ }
236
+ };
237
+ exports.safeParse = safeParse;
@@ -1,6 +1,14 @@
1
+ import path from 'path';
1
2
  export const PAGES_KIT_DID = 'z8iZiDFg3vkkrPwsiba1TLXy3H9XHzFERsP8o';
2
3
  export const PAGES_KIT_RESOURCE_TYPE = 'page';
3
4
  export const PAGES_KIT_BLOCK_STUDIO_DID = 'z2qa7rr3eUyVnWp2PCxEVARuUfLFh6cE5V2xV';
4
5
  export const PAGES_KIT_BLOCK_STUDIO_RESOURCE_TYPE = 'page';
5
6
  export const MEDIA_KIT_DID = 'z8ia1mAXo8ZE7ytGF36L5uBf9kD2kenhqFGp9';
6
7
  export const MEDIA_KIT_RESOURCE_TYPE = 'imgpack';
8
+ export const NEW_BLOCK_TEMPLATE_PATH = path.join(__dirname, 'new-block-template', 'index.tsx');
9
+ export const NEW_BLOCK_TEMPLATE_METADATA_PATH = path.join(__dirname, 'new-block-template', '@metadata.json');
10
+ export const libDir = 'lib';
11
+ export const METADATA_FILE_NAME = '@metadata.json';
12
+ export const PREVIEW_IMAGE_DIR = '@preview-images';
13
+ export const NANOID_LENGTH = 16;
14
+ export const DEFAULT_BLOCK_ENTRY_FILES_PATTERN = 'src/**/index.{ts,tsx,html}';
@@ -0,0 +1,59 @@
1
+ {
2
+ "properties": {
3
+ "gs1rn5jmxfvpxptx": {
4
+ "index": 0,
5
+ "data": {
6
+ "id": "gs1rn5jmxfvpxptx",
7
+ "key": "title",
8
+ "locales": {
9
+ "zh": {
10
+ "name": "Title"
11
+ }
12
+ }
13
+ }
14
+ },
15
+ "9ajrz12ik7esfk1z": {
16
+ "index": 1,
17
+ "data": {
18
+ "id": "9ajrz12ik7esfk1z",
19
+ "key": "description",
20
+ "locales": {
21
+ "zh": {
22
+ "name": "Description",
23
+ "defaultValue": "Welcome to Pages Kit Block Studio"
24
+ }
25
+ }
26
+ }
27
+ },
28
+ "3ckcfvf6b7zyskk8": {
29
+ "index": 2,
30
+ "data": {
31
+ "id": "3ckcfvf6b7zyskk8",
32
+ "key": "logo",
33
+ "type": "url",
34
+ "locales": {
35
+ "zh": {
36
+ "defaultValue": {
37
+ "url": "/.well-known/service/blocklet/logo?imageFilter=convert&f=png&h=80",
38
+ "mediaKitUrl": "/.well-known/service/blocklet/logo?imageFilter=convert&f=png&h=80"
39
+ },
40
+ "name": "Logo"
41
+ }
42
+ }
43
+ }
44
+ },
45
+ "x3lqht8ikble1itx": {
46
+ "index": 3,
47
+ "data": {
48
+ "id": "x3lqht8ikble1itx",
49
+ "key": "copyright",
50
+ "locales": {
51
+ "zh": {
52
+ "defaultValue": "Powered by Pages Kit Block Studio"
53
+ }
54
+ },
55
+ "visible": false
56
+ }
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,89 @@
1
+ import React from 'react';
2
+
3
+ export interface HelloWorldProps {
4
+ title?: string;
5
+ logo?: string | { url: string };
6
+ description?: string;
7
+ copyright?: string;
8
+ }
9
+
10
+ // default export
11
+ export default function HelloWorld({ title = 'Hello World', logo, description, copyright }: HelloWorldProps) {
12
+ return (
13
+ <div
14
+ style={{
15
+ display: 'flex',
16
+ flexDirection: 'column',
17
+ alignItems: 'center',
18
+ padding: '16px 0',
19
+ }}>
20
+ {title && <h1>{title}</h1>}
21
+ {logo && (
22
+ <img
23
+ src={typeof logo === 'object' ? logo.url : logo}
24
+ alt="logo"
25
+ style={{
26
+ margin: '16px 0',
27
+ maxWidth: '200px',
28
+ }}
29
+ />
30
+ )}
31
+ {description && (
32
+ <div
33
+ style={{
34
+ color: '#666',
35
+ marginTop: '8px',
36
+ }}>
37
+ {description}
38
+ </div>
39
+ )}
40
+ {copyright && (
41
+ <div
42
+ style={{
43
+ color: '#999',
44
+ fontSize: '12px',
45
+ marginTop: '16px',
46
+ }}>
47
+ {copyright}
48
+ </div>
49
+ )}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ // export edit component
55
+ export const EditComponent: React.FC<HelloWorldProps & { onChange?: (value: HelloWorldProps) => void }> = ({
56
+ onChange,
57
+ ...props
58
+ }) => {
59
+ return (
60
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
61
+ <div
62
+ style={{
63
+ fontSize: '14px',
64
+ fontWeight: 500,
65
+ color: '#333',
66
+ padding: '8px 0',
67
+ borderBottom: '1px solid #eee',
68
+ }}>
69
+ Footer Parameters
70
+ </div>
71
+ <input
72
+ id="copyright-input"
73
+ type="text"
74
+ style={{
75
+ width: '100%',
76
+ padding: '8px 12px',
77
+ border: '1px solid #ddd',
78
+ borderRadius: '4px',
79
+ fontSize: '14px',
80
+ transition: 'border-color 0.3s',
81
+ outline: 'none',
82
+ }}
83
+ value={props.copyright || ''}
84
+ onChange={(e) => onChange?.({ copyright: e.target.value })}
85
+ placeholder="Please Input Copyright"
86
+ />
87
+ </div>
88
+ );
89
+ };
@@ -11,7 +11,8 @@ import { Router } from 'express';
11
11
  import fs from 'fs';
12
12
  import { isEqual, keyBy, set } from 'lodash';
13
13
  import path from 'path';
14
- import { isPathSafe, isDev, downloadAsset, isMetadataFile, getPreviewImageRelativePath, initializeMetadata, findComponentFiles, getBlockStudioInfo, getBlockCode, } from '../utils/helper';
14
+ import { METADATA_FILE_NAME, NEW_BLOCK_TEMPLATE_PATH, NEW_BLOCK_TEMPLATE_METADATA_PATH } from '../constants';
15
+ import { isPathSafe, isDev, downloadAsset, isMetadataFile, getPreviewImageRelativePath, initializeMetadata, findComponentFiles, getBlockStudioInfo, getBlockCode, getBlockEntryFilesPattern, safeParse, } from '../utils/helper';
15
16
  export const initBlockStudioRouter = Router();
16
17
  const BINARY_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.svg'];
17
18
  initBlockStudioRouter.get('/', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
@@ -83,6 +84,47 @@ initBlockStudioRouter.post('/', (req, res) => __awaiter(void 0, void 0, void 0,
83
84
  return res.status(500).json({ error: 'Failed to write file' });
84
85
  }
85
86
  }));
87
+ initBlockStudioRouter.post('/create', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
88
+ if (!isDev) {
89
+ return res.status(403).json({ error: 'Only available in development mode' });
90
+ }
91
+ const { name, description } = req.body;
92
+ if (!name) {
93
+ return res.status(400).json({ error: 'Name is required' });
94
+ }
95
+ try {
96
+ const pattern = getBlockEntryFilesPattern();
97
+ const baseDir = path.dirname(pattern.replace(/\*\*?/g, ''));
98
+ const blockDir = path.join(baseDir, name);
99
+ const metadataPath = path.join(blockDir, METADATA_FILE_NAME);
100
+ const indexPath = path.join(blockDir, 'index.tsx');
101
+ // Check if block already exists
102
+ if (fs.existsSync(blockDir)) {
103
+ return res.status(409).json({ error: 'Block already exists' });
104
+ }
105
+ // Create block directory
106
+ fs.mkdirSync(blockDir, { recursive: true });
107
+ // Initialize metadata
108
+ let metadata = initializeMetadata(metadataPath);
109
+ metadata.name = name;
110
+ metadata.description = description || '';
111
+ metadata.createdAt = new Date().toISOString();
112
+ metadata.updatedAt = new Date().toISOString();
113
+ // get template metadata from @metadata.json
114
+ const metadataContent = fs.readFileSync(NEW_BLOCK_TEMPLATE_METADATA_PATH, 'utf-8');
115
+ metadata = Object.assign(Object.assign({}, metadata), safeParse(metadataContent));
116
+ // Write metadata file
117
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
118
+ // Copy template file to index.tsx
119
+ const templateContent = fs.readFileSync(NEW_BLOCK_TEMPLATE_PATH, 'utf-8');
120
+ fs.writeFileSync(indexPath, templateContent);
121
+ return res.json({ success: true, metadata });
122
+ }
123
+ catch (error) {
124
+ console.error('Failed to create block:', error);
125
+ return res.status(500).json({ error: 'Failed to create block' });
126
+ }
127
+ }));
86
128
  initBlockStudioRouter.get('/all', (req, res) => __awaiter(void 0, void 0, void 0, function* () {
87
129
  const { withBlockletData = true } = req.query;
88
130
  const allBlocks = yield findComponentFiles();
@@ -164,10 +164,24 @@ ${content.trim()}`;
164
164
  <meta http-equiv="X-Content-Type-Options" content="nosniff">
165
165
  <meta http-equiv="Referrer-Policy" content="no-referrer">
166
166
  <meta http-equiv="Permissions-Policy" content="accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()">`;
167
+ const autoHeightScript = `<script>
168
+ (function() {
169
+ if ('ResizeObserver' in window) {
170
+ const resizeObserver = new ResizeObserver(() => {
171
+ const height = document.documentElement.scrollHeight;
172
+ window.parent.postMessage({ height }, '*');
173
+ });
174
+
175
+ resizeObserver.observe(document.documentElement);
176
+ }
177
+ })();
178
+ </script>`;
167
179
  // 将处理后的相对路径引入的 CSS 和 JS 注入到 HTML 中
168
180
  const htmlContent = htmlWithoutRelativeImport
169
181
  .replace('<head>', `<head>${securityHeaders}`)
170
182
  .replace('</head>', `${cssContents.map((css) => `<style>${css.trim()}</style>`).join('\n')}
183
+
184
+ ${autoHeightScript}
171
185
  </head>`)
172
186
  .replace('</body>', `
173
187
  ${jsContents.map((js) => `<script>${js}</script>`).join('\n')}
@@ -186,19 +200,40 @@ ${content.trim()}`;
186
200
  }
187
201
  export function generateComponent(content, _isDev = true) {
188
202
  const htmlContent = content.html;
189
- const { name } = content;
190
- if (_isDev) {
191
- // do something
192
- }
193
- return `import { createElement } from 'react';
203
+ // const { name } = content;
204
+ return `import React, { useEffect, useRef } from 'react';
205
+
206
+ const htmlContent = ${htmlContent};
194
207
 
195
208
  export default function HtmlPreview() {
196
- return createElement('iframe', {
197
- style: { border: 'none', width: '100%', height: '100%' },
198
- sandbox: 'allow-scripts',
199
- title: 'Preview ${name}',
200
- srcDoc: ${htmlContent}
201
- });
209
+ const iframeRef = useRef(null);
210
+
211
+ useEffect(() => {
212
+ const iframe = iframeRef.current;
213
+ if (!iframe) return;
214
+
215
+ const handleMessage = (event) => {
216
+ if (event.source === iframe.contentWindow) {
217
+ const height = event.data.height;
218
+ if (height) {
219
+ iframe.style.height = height + 'px';
220
+ }
221
+ }
222
+ };
223
+
224
+ window.addEventListener('message', handleMessage);
225
+ return () => window.removeEventListener('message', handleMessage);
226
+ }, []);
227
+
228
+ return (
229
+ <iframe
230
+ ref={iframeRef}
231
+ style={{ border: 'none', width: '100%', height: '0px', maxHeight: '100vh' }}
232
+ sandbox="allow-scripts"
233
+ title="HtmlPreview"
234
+ srcDoc={htmlContent}
235
+ />
236
+ );
202
237
  }`;
203
238
  }
204
239
  export function initHtmlPreviewTransformPlugin() {