@etsoo/website
Version:
ETSOO CMS Based NextJs Website Framework
392 lines (391 loc) • 14.9 kB
JavaScript
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);
}
}
}