1 | ;
|
2 |
|
3 | const Translator = require('./lib/translator');
|
4 | const HandlebarsRenderer = require('@bigcommerce/stencil-paper-handlebars');
|
5 |
|
6 | /**
|
7 | * processor is an optional function to apply during template assembly. The
|
8 | * templates parameter is a object where the keys are paths and the values are the
|
9 | * raw templates. The function returns an object of the same format, possibly changing
|
10 | * the values. We use this to precompile templates within the Paper module.
|
11 | *
|
12 | * @callback processor
|
13 | * @param {Object} templates - Object that contains the gathered templates
|
14 | */
|
15 |
|
16 | /**
|
17 | * Assembler.getTemplates assembles all the templates required to render the given
|
18 | * top-level template.
|
19 | *
|
20 | * @callback assemblerGetTemplates
|
21 | * @param {string} path - The path to the templates, relative to the templates directory
|
22 | * @param {processor} processor - An optional processor to apply to each template during assembly
|
23 | * @return {Promise} A promise to return the (optionally processed) templates
|
24 | */
|
25 |
|
26 | /**
|
27 | * Assembler.getTranslations assembles all the translations for the theme.
|
28 | *
|
29 | * @callback assemblerGetTranslations
|
30 | * @return {Promise} A promise to return the translations
|
31 | */
|
32 |
|
33 | class Paper {
|
34 | /**
|
35 | * Paper constructor. In addition to store settings and theme settings (configuration),
|
36 | * paper expects to be passed an assembler to gather all the templates required to render
|
37 | * the top level template.
|
38 | *
|
39 | * @param {Object} siteSettings - Site settings
|
40 | * @param {Object} themeSettings - Theme settings (configuration)
|
41 | * @param {Object} assembler - Assembler with getTemplates and getTranslations methods.
|
42 | * @param {assemblerGetTemplates} assembler.getTemplates - Method to assemble templates
|
43 | * @param {assemblerGetTranslations} assembler.getTranslations - Method to assemble translations
|
44 | * @param {String} rendererType - One of ['handlebars-v3', 'handlebars-v4']
|
45 | * @param {Object} logger - a console-like logger object
|
46 | * @param {String} logLevel - log level used by handlebars logger (debug, info, warning, error)
|
47 | */
|
48 | constructor(siteSettings, themeSettings, assembler, rendererType, logger = console, logLevel = 'info') {
|
49 | this._assembler = assembler || {};
|
50 |
|
51 | // Build renderer based on type
|
52 | switch(rendererType) {
|
53 | case 'handlebars-v4':
|
54 | this.renderer = new HandlebarsRenderer(siteSettings, themeSettings, 'v4', logger, logLevel);
|
55 | break;
|
56 | case 'handlebars-v3':
|
57 | default:
|
58 | this.renderer = new HandlebarsRenderer(siteSettings, themeSettings, 'v3', logger, logLevel);
|
59 | break;
|
60 | }
|
61 |
|
62 | this.preProcessor = this.renderer.getPreProcessor();
|
63 |
|
64 | this.logger = logger;
|
65 | }
|
66 |
|
67 | /**
|
68 | * Get the siteSettings object containing global site settings.
|
69 | *
|
70 | * @return {object} settings An object containing global site settings.
|
71 | */
|
72 | getSiteSettings() {
|
73 | return this.renderer.getSiteSettings();
|
74 | };
|
75 |
|
76 | /**
|
77 | * Set the siteSettings object containing global site settings.
|
78 | *
|
79 | * @param {object} settings An object containing global site settings.
|
80 | */
|
81 | setSiteSettings(settings) {
|
82 | this.renderer.setSiteSettings(settings);
|
83 | };
|
84 |
|
85 | /**
|
86 | * Get the themeSettings object containing the theme configuration.
|
87 | *
|
88 | * @return {object} settings An object containing the theme configuration.
|
89 | */
|
90 | getThemeSettings() {
|
91 | return this.renderer.getThemeSettings();
|
92 | };
|
93 |
|
94 | /**
|
95 | * Set the themeSettings object containing the theme configuration.
|
96 | *
|
97 | * @param {object} settings An object containing the theme configuration.
|
98 | */
|
99 | setThemeSettings(settings) {
|
100 | this.renderer.setThemeSettings(settings);
|
101 | };
|
102 |
|
103 | /**
|
104 | * Reset decorator list.
|
105 | */
|
106 | resetDecorators() {
|
107 | this.renderer.resetDecorators();
|
108 | };
|
109 |
|
110 | /**
|
111 | * Add a decorator to wrap output during render().
|
112 | *
|
113 | * @param {Function} decorator
|
114 | */
|
115 | addDecorator(decorator) {
|
116 | this.renderer.addDecorator(decorator);
|
117 | };
|
118 |
|
119 | /**
|
120 | * Get page content.
|
121 | *
|
122 | * @return {Object} Regions with widgets
|
123 | */
|
124 | getContent() {
|
125 | return this.renderer.getContent();
|
126 | };
|
127 |
|
128 | /**
|
129 | * Add content to be rendered in the given regions.
|
130 | *
|
131 | * @param {Object} Regions with widgets
|
132 | */
|
133 | setContent(regions) {
|
134 | this.renderer.setContent(regions);
|
135 | };
|
136 |
|
137 | /**
|
138 | * Use the assembler to fetch partials/templates, and translations, then load them
|
139 | * into the renderer.
|
140 | *
|
141 | * @param {String|Array} paths A string or array of strings - the template path(s) to load.
|
142 | * @param {String} acceptLanguage The accept-language header - used to select a locale.
|
143 | * @return {Promise} Promise to load the templates and translations into the renderer.
|
144 | */
|
145 | loadTheme(paths, acceptLanguage) {
|
146 | if (!Array.isArray(paths)) {
|
147 | paths = paths ? [paths] : [];
|
148 | }
|
149 |
|
150 | const promises = [];
|
151 | promises.push(this.loadTranslations(acceptLanguage));
|
152 | paths.forEach(path => {
|
153 | promises.push(this.loadTemplates(path));
|
154 | });
|
155 |
|
156 | return Promise.all(promises);
|
157 | }
|
158 |
|
159 | /**
|
160 | * Use the assembler to fetch partials/templates, then load them
|
161 | * into the renderer.
|
162 | *
|
163 | * @param {String} path The root template path to load. All dependencies will be loaded as well.
|
164 | * @return {Promise} Promise to load the templates into the renderer.
|
165 | */
|
166 | loadTemplates(path) {
|
167 | return this._assembler.getTemplates(path, this.preProcessor).then(templates => {
|
168 | return this.renderer.addTemplates(templates);
|
169 | });
|
170 | }
|
171 |
|
172 | /**
|
173 | * Is the given template loaded?
|
174 | *
|
175 | * @param {String} path The path to the template file
|
176 | * @return {Boolean} is the given template loaded?
|
177 | */
|
178 | isTemplateLoaded(path) {
|
179 | return this.renderer.isTemplateLoaded(path);
|
180 | }
|
181 |
|
182 | /**
|
183 | * Load translation files and give a translator to renderer.
|
184 | *
|
185 | * @param {String} acceptLanguage The accept-language header, used to select a locale
|
186 | * @return {Promise} Promise to load the translations into the renderer.
|
187 | */
|
188 | loadTranslations(acceptLanguage) {
|
189 | return this._assembler.getTranslations().then(translations => {
|
190 | const translator = Translator.create(acceptLanguage, translations, this.logger);
|
191 | this.renderer.setTranslator(translator);
|
192 | return translations;
|
193 | });
|
194 | };
|
195 |
|
196 | /**
|
197 | * Render a string with the given context.
|
198 | *
|
199 | * @param {String} string
|
200 | * @param {Object} context
|
201 | * @return {Promise} A promise to return the rendered text
|
202 | * @throws [CompileError|RenderError]
|
203 | */
|
204 | renderString(string, context) {
|
205 | return this.renderer.renderString(string, context);
|
206 | }
|
207 |
|
208 | /**
|
209 | * Renders a template with the given context
|
210 | *
|
211 | * @param {String} path The path to the template
|
212 | * @param {Object} context The context to provide to the template
|
213 | * @return {Promise} A promise to return the rendered template
|
214 | * @throws [TemplateNotFoundError|RenderError|DecoratorError]
|
215 | */
|
216 | render(path, context) {
|
217 | return this.renderer.render(path, context);
|
218 | }
|
219 |
|
220 | /**
|
221 | * Theme rendering logic. This is used by Stencil CLI.
|
222 | *
|
223 | * @param {String|Array} templatePath A single template or list of templates to render.
|
224 | * @param {Object} data
|
225 | * @return {Promise} A promise that returns a {String|Object} depending on whether multiple templates were rendered
|
226 | * @throws [TemplateNotFoundError|RenderError|DecoratorError]
|
227 | */
|
228 | renderTheme(templatePath, data) {
|
229 | // Simple case of a single non-ajax template
|
230 | if (!data.remote && !Array.isArray(templatePath)) {
|
231 | return this.render(templatePath, data.context);
|
232 | }
|
233 |
|
234 | // If no template path provided, just return the remote data
|
235 | if (!templatePath) {
|
236 | return Promise.resolve({ data: data.remote_data });
|
237 | }
|
238 |
|
239 | // If ajax request, merge remote_data into context
|
240 | if (data.remote) {
|
241 | data.context = Object.assign({}, data.context, data.remote_data);
|
242 | }
|
243 |
|
244 | const renderPromises = [];
|
245 | let result;
|
246 |
|
247 | if (Array.isArray(templatePath)) {
|
248 | // If templatePath is an array (multiple templates using render_with option),
|
249 | // compile all the template required files into an object
|
250 | result = {};
|
251 | for (let i = 0; i < templatePath.length; i++) {
|
252 | const path = templatePath[i];
|
253 | renderPromises.push(this.render(path, data.context).then(html => {
|
254 | result[path] = html;
|
255 | }));
|
256 | }
|
257 | } else {
|
258 | renderPromises.push(this.render(templatePath, data.context).then(html => {
|
259 | result = html;
|
260 | }));
|
261 | }
|
262 |
|
263 | return Promise.all(renderPromises).then(() => {
|
264 | // Remote requests get both the remote data & rendered html
|
265 | if (data.remote) {
|
266 | result = {
|
267 | data: data.remote_data,
|
268 | content: result
|
269 | };
|
270 | }
|
271 |
|
272 | return result;
|
273 | });
|
274 | }
|
275 | }
|
276 |
|
277 | module.exports = Paper;
|