UNPKG

gpreview

Version:

Preview Ghost themes locally without a full Ghost installation

319 lines • 12.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PreviewServer = void 0; const express_1 = __importDefault(require("express")); const path_1 = __importDefault(require("path")); const cors_1 = __importDefault(require("cors")); const themeLoader_1 = require("../theme/themeLoader"); const handlebarsRenderer_1 = require("../renderer/handlebarsRenderer"); const mockData_1 = require("../data/mockData"); const promises_1 = __importDefault(require("fs/promises")); const fs_1 = require("fs"); class PreviewServer { app; themeLoader; renderer; theme; mockData; port; constructor(themePath, options = {}) { this.app = (0, express_1.default)(); this.themeLoader = new themeLoader_1.ThemeLoader(themePath); this.renderer = new handlebarsRenderer_1.HandlebarsRenderer(); this.port = options.port || 3000; this.mockData = mockData_1.defaultMockData; if (options.dataPath) { this.loadCustomData(options.dataPath); } } async loadCustomData(dataPath) { try { if ((0, fs_1.existsSync)(dataPath)) { const content = await promises_1.default.readFile(dataPath, 'utf-8'); const customData = JSON.parse(content); this.mockData = { ...mockData_1.defaultMockData, ...customData }; if (process.env.NODE_ENV !== 'test') { console.log(`Loaded custom data from ${dataPath}`); } } } catch (error) { if (process.env.NODE_ENV !== 'test') { console.error(`Failed to load custom data: ${error}`); } } } async initialize() { this.theme = await this.themeLoader.load(); await this.renderer.initialize(this.theme); this.setupMiddleware(); this.setupRoutes(); this.setupErrorHandling(); } setupMiddleware() { this.app.use((0, cors_1.default)()); this.app.use(express_1.default.json()); this.app.use(express_1.default.urlencoded({ extended: true })); if (this.theme) { const assetsPath = path_1.default.join(this.theme.themePath, 'assets'); if ((0, fs_1.existsSync)(assetsPath)) { this.app.use('/assets', express_1.default.static(assetsPath)); } const cssPath = path_1.default.join(this.theme.themePath, 'assets', 'css'); const jsPath = path_1.default.join(this.theme.themePath, 'assets', 'js'); const imagesPath = path_1.default.join(this.theme.themePath, 'assets', 'images'); if ((0, fs_1.existsSync)(cssPath)) { this.app.use('/css', express_1.default.static(cssPath)); } if ((0, fs_1.existsSync)(jsPath)) { this.app.use('/js', express_1.default.static(jsPath)); } if ((0, fs_1.existsSync)(imagesPath)) { this.app.use('/images', express_1.default.static(imagesPath)); } } } setupRoutes() { this.app.get('/placeholder/:dimensions', (req, res) => { const { dimensions } = req.params; const [width, height] = dimensions.split('x').map(Number); if (!width || !height || width > 2000 || height > 2000) { return res.status(400).send('Invalid dimensions'); } const svg = ` <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg"> <rect width="100%" height="100%" fill="#f0f0f0"/> <text x="50%" y="50%" text-anchor="middle" dy="0.3em" font-family="Arial, sans-serif" font-size="${Math.min(width, height) / 10}px" fill="#999">${width}Ɨ${height}</text> </svg> `; res.setHeader('Content-Type', 'image/svg+xml'); res.setHeader('Cache-Control', 'public, max-age=31536000'); res.send(svg); }); this.app.get('/', async (req, res, next) => { try { const data = { site: this.mockData.site, posts: this.mockData.posts, tags: this.mockData.tags, authors: this.mockData.authors, pagination: { page: 1, limit: 10, pages: Math.ceil((this.mockData.posts?.length || 0) / 10), total: this.mockData.posts?.length || 0, }, postFeedStyle: 'Grid', }; const html = await this.renderer.render('index', data, { bodyClass: 'home-template', }); res.send(html); } catch (error) { next(error); } }); this.app.get('/post/:slug', async (req, res, next) => { try { const { slug } = req.params; const post = this.mockData.posts?.find((p) => p.slug === slug); if (!post) { return this.render404(res); } const data = { site: this.mockData.site, post, tags: this.mockData.tags, authors: this.mockData.authors, }; const html = await this.renderer.render('post', data, { bodyClass: 'post-template', }); res.send(html); } catch (error) { next(error); } }); this.app.get('/:slug', async (req, res, next) => { try { const { slug } = req.params; const page = this.mockData.pages?.find((p) => p.slug === slug); if (page) { const data = { site: this.mockData.site, page, tags: this.mockData.tags, authors: this.mockData.authors, }; const templateName = page.custom_template || 'page'; const html = await this.renderer.render(templateName, data, { bodyClass: `page-template page-${slug}`, }); return res.send(html); } const post = this.mockData.posts?.find((p) => p.slug === slug); if (post) { const data = { site: this.mockData.site, post, tags: this.mockData.tags, authors: this.mockData.authors, }; const html = await this.renderer.render('post', data, { bodyClass: 'post-template', }); return res.send(html); } this.render404(res); } catch (error) { next(error); } }); this.app.get('/tag/:slug', async (req, res, next) => { try { const { slug } = req.params; const tag = this.mockData.tags?.find((t) => t.slug === slug); if (!tag) { return this.render404(res); } const posts = this.mockData.posts?.filter((post) => post.tags.some((t) => t.slug === slug)) || []; const data = { site: this.mockData.site, tag, posts, tags: this.mockData.tags, authors: this.mockData.authors, pagination: { page: 1, limit: 10, pages: Math.ceil(posts.length / 10), total: posts.length, }, }; const html = await this.renderer.render('tag', data, { bodyClass: `tag-template tag-${slug}`, }); res.send(html); } catch (error) { next(error); } }); this.app.get('/author/:slug', async (req, res, next) => { try { const { slug } = req.params; const author = this.mockData.authors?.find((a) => a.slug === slug); if (!author) { return this.render404(res); } const posts = this.mockData.posts?.filter((post) => post.authors.some((a) => a.slug === slug)) || []; const data = { site: this.mockData.site, author, posts, tags: this.mockData.tags, authors: this.mockData.authors, pagination: { page: 1, limit: 10, pages: Math.ceil(posts.length / 10), total: posts.length, }, }; const html = await this.renderer.render('author', data, { bodyClass: `author-template author-${slug}`, }); res.send(html); } catch (error) { next(error); } }); this.app.get('/page/:slug', async (req, res, next) => { try { const { slug } = req.params; const page = this.mockData.pages?.find((p) => p.slug === slug); if (!page) { return this.render404(res); } const data = { site: this.mockData.site, page, }; const templateName = page.custom_template || 'page'; const html = await this.renderer.render(templateName, data, { bodyClass: `page-template page-${slug}`, }); res.send(html); } catch (error) { next(error); } }); } async render404(res) { try { const data = { site: this.mockData.site, }; const templateName = this.theme?.templates.has('404') ? '404' : 'index'; const html = await this.renderer.render(templateName, data, { bodyClass: 'error-template error-404', }); res.status(404).send(html); } catch (_error) { res.status(404).send('<h1>404 - Page Not Found</h1>'); } } setupErrorHandling() { this.app.use((error, req, res, _next) => { console.error('Server error:', error); try { const data = { site: this.mockData.site, }; const templateName = this.theme?.templates.has('error') ? 'error' : 'index'; this.renderer.render(templateName, data, { bodyClass: 'error-template error-500', }).then(html => { res.status(500).send(html); }).catch(() => { res.status(500).send('<h1>500 - Internal Server Error</h1>'); }); } catch { res.status(500).send('<h1>500 - Internal Server Error</h1>'); } }); } async start() { await this.initialize(); this.app.listen(this.port, () => { console.log(`\\nšŸŽØ Ghost Theme Preview Server`); console.log(`šŸ“ Theme: ${this.theme?.themePath}`); console.log(`🌐 Server: http://localhost:${this.port}`); console.log(`\\nAvailable templates:`); this.theme?.templates.forEach((_, name) => { console.log(` - ${name}.hbs`); }); if (this.theme?.partials.size) { console.log(`\\nAvailable partials:`); this.theme?.partials.forEach((_, name) => { console.log(` - ${name}`); }); } console.log('\\nPress Ctrl+C to stop\\n'); }); } } exports.PreviewServer = PreviewServer; //# sourceMappingURL=server.js.map