UNPKG

@etsoo/website

Version:

ETSOO CMS Based NextJs Website Framework

392 lines (391 loc) 14.9 kB
import { NotificationMessageType } from '@etsoo/notificationbase'; import { createClient } from '@etsoo/restclient'; import { DomUtils, Utils } from '@etsoo/shared'; import { wxe } from '@etsoo/weixin'; import { NotifierContainer } from '../notifier/NotifierContainer'; import { SiteUtils } from './SiteUtils'; /** * Client site */ export class ClientSite { /** * Is ready * 是否准备就绪 */ get isReady() { return this._isReady; } /** * Constructor * 构造函数 * @param culture Culture, like en, zh-Hans * @param apiUrl Headless CMS API Url * @param errorHandler Custom error handler */ constructor(culture, apiUrl, errorHandler) { this.culture = culture; this._isReady = false; // Notifier this.notifier = new NotifierContainer(); // Default error hanlder errorHandler !== null && errorHandler !== void 0 ? errorHandler : (errorHandler = (e) => this.notifier.message(NotificationMessageType.Danger, e.message, 'API error')); const api = createClient(); api.baseUrl = apiUrl; api.onError = errorHandler; // Add content-language header api.setContentLanguage(culture); this.api = api; } /** * Dispose * 释放资源 */ dispose() { this.notifier.dispose(); if (this.backToTopDispose) { this.backToTopDispose(); this.backToTopDispose = undefined; } if (this.windowScrollDispose) { this.windowScrollDispose(); this.windowScrollDispose = undefined; } if (this.formSubmitDispose) { this.formSubmitDispose(); this.formSubmitDispose = undefined; } this._isReady = false; } /** * Get document meta content * 获取文档 Meta 内容 * @param name Name * @returns content */ getMeta(name) { var _a; return (_a = document.querySelector(`meta[name="${name}"]`)) === null || _a === void 0 ? void 0 : _a.content; } /** * Get document Open Graph * https://ogp.me/ * 获取文档 Open Graph 内容 * @param name Name * @returns content */ getOG(name) { var _a; return (_a = document.querySelector(`meta[property="og:${name}"]`)) === null || _a === void 0 ? void 0 : _a.content; } /** * Get Google reCaptcha token * @param action Action * @param callback Callback */ grecaptcha(action, callback) { if (typeof grecaptcha == undefined || typeof globalThis.googleGecaptchaSiteKey == undefined) { callback(''); return; } grecaptcha.ready(function () { grecaptcha .execute(globalThis.googleGecaptchaSiteKey, { action: action }) .then(function (token) { callback(token); }); }); } /** * Send email * 发送邮件 * @param rq Request data * @param api Function API * @returns Result */ async sendEmail(rq, api = 'Public/SendEmail') { // Pass the JSON data if (typeof rq.data === 'object') rq.data = JSON.stringify(rq.data); // API call return await this.api.post(api, rq); } /** * Setup * @param resources Custom resources * @param options Setup options */ setup(resources = {}, options) { // Spinner const spinner = document.getElementById('spinner'); spinner === null || spinner === void 0 ? void 0 : spinner.classList.remove('show'); // Check ready to avoid multiple setup if (this.isReady) return; // Destruct options const { drawflowStyle, drawVersion, ...actions } = options !== null && options !== void 0 ? options : {}; // Setup utils SiteUtils.setup(this.culture, resources); // Setup scroll actions this.setupScrollActions(actions); // Setup contact form this.setupContactForm(); // Setup drawflow viewer this.setupDrawflowViewer(drawflowStyle, drawVersion); // Ready this._isReady = true; } /** * Setup scroll actions * 设置滚动动作 * @param actions Scroll actions */ setupScrollActions(actions) { const { backToTopSelector = '.back-to-top', backToTopThreshold = 300, stickyTopSelector = '.sticky-top', stickyTopThreshold = 100 } = actions !== null && actions !== void 0 ? actions : {}; const stickyNav = document.querySelector(stickyTopSelector); const gotoTop = document.querySelector(backToTopSelector); if (gotoTop) { const backToTopHandler = () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }; gotoTop.addEventListener('click', backToTopHandler); this.backToTopDispose = () => { gotoTop.removeEventListener('click', backToTopHandler); }; } if (stickyNav || gotoTop) { const windowScrollHandler = () => { if (stickyNav) { if (window.scrollY > stickyTopThreshold) { stickyNav.style.top = '0px'; } else { stickyNav.style.top = `${-stickyTopThreshold}px`; } } if (gotoTop) { if (window.scrollY > backToTopThreshold) { gotoTop.style.display = 'flex'; } else { gotoTop.style.display = 'none'; } } }; window.addEventListener('scroll', windowScrollHandler); this.windowScrollDispose = () => { window.removeEventListener('scroll', windowScrollHandler); }; } } /** * Setup contact form * @param selectors Form selectors, default is "form[name='contact-form']" * @param recipientField Recipient field name, default is "email" * @param templateName Email template name */ setupContactForm(selectors = "form[name='contact-form']", recipientField = 'email', templateName) { const form = document.querySelector(selectors); if (form) { // Labels with 'for' attribute const labels = form.querySelectorAll('label[for]'); labels.forEach((label) => { if (label.control && 'required' in label.control && label.control.required) { label.classList.add('form-label-required'); } }); if (typeof form.name !== 'string') { this.notifier.alert('The form name is required. And please do not name a form field with "name".'); return; } // Default values from query string const searchParams = new URLSearchParams(location.search); searchParams.forEach((value, key) => { const field = form.querySelector(`[name="${key}"]`); if (field) field.value = value; }); // Action (A-Za-z/_) from form name const action = form.name .replace('-', '_') .replace(/[^A-Za-z_]/g, ''); // Default email template name const template = templateName !== null && templateName !== void 0 ? templateName : `${action.toUpperCase()}_EMAIL_TEMPLATE`; // Submit handler const submitHandler = (event) => { event.preventDefault(); const recipentField = form.querySelector(`[name="${recipientField}"]`); if (recipentField == null) { this.notifier.alert('Recipient field not found'); return; } // Form data const data = Object.fromEntries(new FormData(form)); // Remove empty values Utils.removeEmptyValues(data); // Check recipient const recipient = data[recipientField].toString(); if (!recipient.isEmail()) { recipentField.focus(); return; } const submitButton = form.querySelector('button[type="submit"]'); if (submitButton) SiteUtils.toggleButtonSpinner(submitButton); this.grecaptcha(action, async (token) => { var _a, _b, _c; const result = await this.sendEmail({ recipient, template, data, token }); if (result) { if (result.ok) { this.notifier.alert((_b = (_a = result.data) === null || _a === void 0 ? void 0 : _a.successMessage) !== null && _b !== void 0 ? _b : 'Your enquiry has been sent successfully', () => form.reset(), NotificationMessageType.Success); } else { this.notifier.alert((_c = result.title) !== null && _c !== void 0 ? _c : 'Error'); } } if (submitButton) SiteUtils.toggleButtonSpinner(submitButton); }); }; form.addEventListener('submit', submitHandler); this.formSubmitDispose = () => { form.removeEventListener('submit', submitHandler); }; } } /** * Setup drawflow viewer * 设置 Drawflow 视图 * @param drawflowStyle Drawflow style * @param drawVersion Drawflow version */ setupDrawflowViewer(drawflowStyle, drawVersion) { var _a; // Check the export data container const dataContainer = document.querySelector("pre[name='drawflow-data']"); if (dataContainer == null) return; // Copy styles const iframe = document.createElement('iframe'); iframe.style.width = dataContainer.style.width; iframe.style.height = dataContainer.style.height; // Copy classes dataContainer.classList.forEach((c) => { if (c === 'd-none') return; iframe.classList.add(c); }); // Parent font-awesome icons, keep the same version and avoid duplicate loading const fontAwesome = document.querySelector('link[rel="stylesheet"][href*="/font-awesome"]'); // Drawflow iframe drawflowStyle !== null && drawflowStyle !== void 0 ? drawflowStyle : (drawflowStyle = '/drawflow.css'); const version = drawVersion ? `@${drawVersion}` : ''; const html = `<!DOCTYPE html> <html> <head> <link href="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow${version}/dist/drawflow.min.css" rel="stylesheet" /> <link href="${drawflowStyle}" rel="stylesheet" /> ${fontAwesome ? `<link href="${fontAwesome.href}" rel="stylesheet" />` : ''} <script src="https://cdn.jsdelivr.net/gh/jerosoler/Drawflow${version}/dist/drawflow.min.js"></script> </head> <body> </body> <script> (function() { const jsonData = ${dataContainer.innerHTML}; const drawflow = new Drawflow(document.body); drawflow.editor_mode = "view"; drawflow.start(); drawflow.import(jsonData); })(); </script> </html> `; if ('srcdoc' in iframe && !DomUtils.isWechatClient()) { iframe.srcdoc = html; dataContainer.after(iframe); } else { // contentWindow is null when the iframe is not in the DOM dataContainer.after(iframe); const doc = (_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.document; if (doc) { doc.open(); doc.write(html); doc.close(); } } dataContainer.remove(); } /** * Setup wechat share * 设置微信分享 * @param share Shared data * @param api Wechat configuration API * @returns Result */ async setupWechat(share, api = 'Public/CreateJsApiSignature') { var _a, _b, _c, _d, _e, _f; try { // Load config const data = await this.api.put(api, { url: location.href }, { showLoading: false }); if (data == null) return; // Check exists if (typeof wx === undefined) return; // Apis const apis = [ 'updateAppMessageShareData', 'onMenuShareAppMessage', 'updateTimelineShareData', 'onMenuShareTimeline' ]; // Config const result = await wxe.configAsync({ ...data, jsApiList: apis }); if (result != null) { console.log('wxe.configAsync', result); return; } // Check const ckResult = await wxe.checkJsApiAsync({ jsApiList: apis }); if (!ckResult.errMsg.endsWith('ok')) { console.log('checkJsApiAsync', ckResult.errMsg); } // Share data if (share == null) { const title = (_a = this.getOG('title')) !== null && _a !== void 0 ? _a : document.title; const link = (_b = this.getOG('url')) !== null && _b !== void 0 ? _b : location.href; const desc = (_d = (_c = this.getOG('description')) !== null && _c !== void 0 ? _c : this.getMeta('description')) !== null && _d !== void 0 ? _d : ''; let imgUrl = (_f = (_e = this.getOG('image')) !== null && _e !== void 0 ? _e : this.getMeta('image_src')) !== null && _f !== void 0 ? _f : '/og.jpg'; if (!imgUrl.includes('://')) imgUrl = location.protocol + '//' + location.host + imgUrl; share = { title, link, imgUrl, desc }; } // Setup share wxe.setupShare(share, ckResult.checkResult); } catch (e) { console.log('WX setup failed with an error', e); } } }