UNPKG

msc-ai-assistant

Version:

<msc-ai-assistant /> is a web component based on Chrome Built-in AI Prompt API. Web developers could use <msc-ai-assistant /> to help user consult anything they like to know.

1,054 lines (952 loc) 28.6 kB
export const _wcl = {}; Object.defineProperties(_wcl, { getUUID: { configurable: true, enumerable: true, value: function() { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ); } }, getWCConfig: { configurable: true, enumerable: true, value: async function(host) { // gather web component's config from [remoteconfig] or inner script[type=application/json] let error, config; const remoteconfig = host.getAttribute('remoteconfig'); const script = host.querySelector(':scope > script[type="application/json"]'); if (remoteconfig) { // fetch remote config once [remoteconfig] exist try { const configUrl = new URL(remoteconfig); config = await fetch(configUrl.toString(), { headers: { 'content-type': 'application/json' }, method: 'GET', mode: 'cors' }) .then( (response) => { if (!response.ok) { throw new Error('Network response was not OK'); } return response.json(); } ) .catch( (err) => { error = err.message; return {}; } ); } catch(err) { error = err.message; } } else if (script) { // apply inner script's config try { config = JSON.parse(script.textContent.replace(/\n/g, '').trim()); } catch(err) { error = err.message; } } return { config, error }; } }, whenDefined: { configurable: true, enumerable: true, value: function() { /** * Check lib ready * @example whenDefined('document.body', 'YT.Player').then(...) */ const units = Array.from(arguments); if (units.indexOf('document.body') === -1) { units.push('document.body'); } const promises = units.map( (unit) => { return new Promise((resolve, reject) => { let c, iid; const max = 10000; c = 0; iid = setInterval(() => { let _root, parts; c += 5; if (c > max) { clearInterval(iid); return reject(new Error(`"${unit}" unit missing.`)); } _root = window; parts = unit.split('.'); while (parts.length) { const prop = parts.shift(); if (typeof _root[prop] === 'undefined') { _root = null; break; } else { _root = _root[prop]; } } if (_root !== null && document.readyState && document.readyState !== 'loading') { clearInterval(iid); return resolve(); } }, 5); }); } ); return Promise.all(promises); } }, loadScript: { configurable: true, enumerable: true, value: function() { /** * Load script(s) to the document * @example loadScript('script-path') loadScript('script-path1', 'script-path2') */ const currentScripts = Array.from(document.getElementsByTagName('script')).map( (script) => script.src ); Array.from(arguments).forEach( (path) => { if (currentScripts.includes(path)) { return; } const script = document.createElement('script'); document.head.appendChild(script); script.async = true; script.src = path; } ); } }, camelCase: { configurable: true, enumerable: true, value: function(str) { return str.replace(/-([a-z])/ig, function(all, letter) { return letter.toUpperCase(); } ); } }, capitalize: { configurable: true, enumerable: true, value: function(str) { return str.replace(/^[a-z]{1}/, function($1) { return $1.toUpperCase(); } ); } }, rgbToHex: { configurable: true, enumerable: true, value: function(r, g, b) { /* * https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb * ex: * rgbToHex(255, 0, 0) */ return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } }, hexToRgb: { configurable: true, enumerable: true, value: function(hex) { /* * https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb * ex: * hexToRgb("#0033ff").g * hexToRgb("#03f").g */ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, function(m, r, g, b) { return r + r + g + g + b + b; }); const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } }, getLuminosity: { configurable: true, enumerable: true, value: function(hex) { /* * https://codepen.io/borlyjenkins/pen/dyPXjPr * https://github.com/Qix-/color * ex: * getLuminosity("#0033ff") * luminosity < 0.57 ? white-text : black-text */ const { r, g, b } = this.hexToRgb(hex); return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; } }, classToTagName: { configurable: true, enumerable: true, value: function(str) { return str.replace(/([A-Z])/g, function(all, letter) { return '-' + letter.toLowerCase(); } ).replace(/^-(.*)/, '$1'); } }, isObject: { configurable: true, enumerable: true, value: (value) => value && typeof value === 'object' && value.constructor === Object }, isNumeric: { configurable: true, enumerable: true, value: (value) => !isNaN(value - parseFloat(value)) }, isAPISupport: { configurable: true, enumerable: true, value: function(apiName, element) { let node, flag; navigator.supports = navigator.supports || {}; navigator.supports.api = navigator.supports.api || {}; if (typeof navigator.supports.api[apiName] !== 'undefined') { flag = navigator.supports.api[apiName]; } else { if (element) { node = (element.tagName) ? element.cloneNode(true) : element; } else { node = window; } flag = ['', 'webkit', 'moz', 'o', 'ms'].find( (key) => { let s; s = key + (key ? this.capitalize(apiName) : apiName); return node[s]; } ); flag = (flag !== undefined) ? true : false; //localStorage if (['localStorage', 'sessionStorage'].includes(apiName) && flag) { try { localStorage.setItem('isapisupport', 'dummy'); localStorage.removeItem('isapisupport'); } catch(err) { flag = false; } }//end if navigator.supports.api[apiName] = flag; node = null; } return flag; } }, isEventSupport: { configurable: true, enumerable: true, value: function(eventName, element) { /** * reference: * http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ * @example * isEventSupport('touchstart'); */ let node, flag; navigator.supports = navigator.supports || {}; navigator.supports.event = navigator.supports.event || {}; if (typeof navigator.supports.event[eventName] !== 'undefined') { flag = navigator.supports.event[eventName]; } else { if (element) { node = (element.tagName) ? element.cloneNode(true) : element; } else { node = document.createElement('div'); } flag = `on${eventName}` in node; if (!flag && node.setAttribute) { node.setAttribute(eventName, 'return;'); flag = typeof node[eventName] == 'function'; } navigator.supports.event[eventName] = flag; node = null; } return flag; } }, isCSSPropertySupport: { configurable: true, enumerable: true, value: function(property) { /** * @example * isCSSPropertySupport('overscroll-behavior'); * * otherwise, maybe we can try CSS.supports * https://developer.mozilla.org/en-US/docs/Web/API/CSS/supports */ let node, flag; navigator.supports = navigator.supports || {}; navigator.supports.css = navigator.supports.css || {}; property = (/^-ms/.test(property)) ? ('ms' + this.camelCase(property.replace(/-ms/,''))) : this.camelCase(property); if (typeof navigator.supports.css[property] !== 'undefined') { flag = navigator.supports.css[property]; } else { node = document.createElement('div'); flag = property in node.style; navigator.supports.css[property] = flag; node = null; } return flag; } }, isStaticImportSupport:{ configurable: true, enumerable: true, value: () => { /* * https://gist.github.com/ebidel/3201b36f59f26525eb606663f7b487d0 */ navigator.supports = navigator.supports || {}; if (typeof navigator.supports.staticImport === 'undefined') { const script = document.createElement('script'); navigator.supports.staticImport = 'noModule' in script; } return navigator.supports.staticImport; } }, isDynamicImportSupport:{ configurable: true, enumerable: true, value: () => { /* * https://gist.github.com/ebidel/3201b36f59f26525eb606663f7b487d0 */ navigator.supports = navigator.supports || {}; if (typeof navigator.supports.dynamicImport === 'undefined') { try { new Function('import("")'); navigator.supports.dynamicImport = true; } catch (err) { navigator.supports.dynamicImport = false; } } return navigator.supports.dynamicImport; } }, isPrefersColorSchemeSet: { configurable: true, enumerable: true, get: () => { return Array.from(document.styleSheets).some( (styleSheet) => { try { return Array.from(styleSheet.rules).some( (rule) => { return rule.media && /prefers-color-scheme:.*dark/i.test(rule.conditionText); } ); } catch(err) { return false; } } ); } }, isIOS: { configurable: true, enumerable: true, get: () => !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform) }, isMobile: { configurable: true, enumerable: true, value: () => navigator.userAgent.match(/(iPhone|iPod|iPad|Android|webOS|BlackBerry|IEMobile|Opera Mini)/i) || false }, grabEventLock: { configurable: true, enumerable: true, value: function() { return navigator.eventLock || (function() { this.addStylesheetRules('.scroll-lock', { 'overflow': 'hidden', 'pointer-events': 'none' }); navigator.eventLock = (evt) => { evt.preventDefault(); evt.stopImmediatePropagation(); }; return navigator.eventLock; }).bind(this)(); } }, scrollLock: { configurable: true, enumerable: true, value: function(isLock = true, eventLock = this.grabEventLock()) { isLock = Boolean(isLock); if (isLock) { document.body.classList.add('scroll-lock'); window.addEventListener('touchmove', eventLock, { passive: false }); } else { document.body.classList.remove('scroll-lock'); window.removeEventListener('touchmove', eventLock); } } }, scrollX: { configurable: true, enumerable: true, get: () => { return _wcl.getScroll().x; }, set: (x) => { if (document.documentElement && document.documentElement.scrollLeft) { document.documentElement.scrollLeft = x; } else { document.body.scrollLeft = x; } } }, scrollY: { configurable: true, enumerable: true, get: () => { return _wcl.getScroll().y; }, set: (y) => { if (document.documentElement && document.documentElement.scrollTop) { document.documentElement.scrollTop = y; } else { document.body.scrollTop = y; } } }, rollTo: { configurable: true, enumerable: true, value: function(y, offset = 0) { /* * y could be y-coord or DOM element */ const { height } = this.getPageSize(); const { height:winHeight } = this.getViewportSize(); if (typeof y.nodeType !== 'undefined' && y.nodeType === 1 && typeof y.getBoundingClientRect === 'function') { y = this.getPosition(y).y; } y += offset; return new Promise((resolve) => { let iid; const scroll = () => { let shift = Math.ceil((y - this.scrollY) * 0.15); cancelAnimationFrame(iid); scrollBy(0, shift); if (this.scrollY < 0 || this.scrollY + winHeight >= height || shift === 0 || Math.abs(this.scrollY - y) <= Math.abs(shift)) { this.scrollY = y; resolve(); return; } else { iid = requestAnimationFrame(scroll); } }; iid = requestAnimationFrame(scroll); }); } }, maxZIndex: { configurable: true, enumerable: true, get: () => { return Array.from(document.querySelectorAll('body *')) .map((a) => parseFloat(window.getComputedStyle(a).zIndex)) .filter((a) => !isNaN(a)) .sort((a,b) => a - b) .pop(); } }, pointer: { configurable: true, enumerable: true, value: function(e) { let x, y, clientX, clientY, docElement, body; docElement = document.documentElement; if (e?.touches && e?.touches?.length > 0) { clientX = e?.touches?.[0]?.clientX; clientY = e?.touches?.[0]?.clientY; } else { clientX = e.clientX; clientY = e.clientY; } //x body = document.body || { scrollLeft: 0 }; x = e?.pageX || (clientX + (docElement.scrollLeft || body.scrollLeft) - (docElement.clientLeft || 0)); //y body = document.body || { scrollTop: 0 }; y = e?.pageY || (clientY + (docElement.scrollTop || body.scrollTop) - (docElement.clientTop || 0)); return { x, y }; } }, pursuer: { configurable: true, enumerable: true, value: function() { let down, move, up; if (this.isEventSupport('touchstart')) { down = 'touchstart'; move = 'touchmove'; up = 'touchend'; } else if (typeof navigator.msPointerEnabled != 'undefined' && navigator.msPointerEnabled) { down = 'MSPointerDown'; move = 'MSPointerMove'; up = 'MSPointerUp'; } else { down = 'mousedown'; move = 'mousemove'; up = 'mouseup'; } return {down, move, up}; } }, purgeObject: { configurable: true, enumerable: true, value: function(targetObject) { if (this.isObject(targetObject)) { Object.keys(targetObject).forEach( (key) => { let { [key]:prop } = targetObject; if (Array.isArray(prop)) { while (prop.length) { prop.pop(); }// end while }// end if targetObject[key] = null; } ); }// end if } }, isPiPSupport: { configurable: true, enumerable: true, value: function() { navigator.supports = navigator.supports || {}; if (typeof navigator.supports.PiP === 'undefined') { const node = document.createElement('video'); navigator.supports.PiP = this.isAPISupport('requestPictureInPicture', node) || (node.webkitSupportsPresentationMode && typeof node.webkitSetPresentationMode === 'function'); } return navigator.supports.PiP; } }, isFullscreenSupport: { configurable: true, enumerable: true, value: function() { navigator.supports = navigator.supports || {}; if (!navigator.supports.fullscreen) { const node = document.createElement('div'); let request = false; let exit = false; let element = ''; let event = ''; if (node.requestFullscreen) { request = 'requestFullscreen'; exit = 'exitFullscreen'; element = 'fullscreenElement'; event = 'fullscreenchange'; // } else if (node.msRequestFullscreen) { // request = 'msRequestFullscreen'; // exit = 'msExitFullscreen'; } else if (node.webkitRequestFullscreen) { request = 'webkitRequestFullscreen'; exit = 'webkitExitFullscreen'; element = 'webkitFullscreenElement'; event = 'webkitfullscreenchange'; } navigator.supports.fullscreen = { request, exit, element, event }; } return navigator.supports.fullscreen; } }, supports: { configurable: true, enumerable: true, value: function() { // let flag; navigator.supports = navigator.supports || {}; if (!navigator.supports.wp) { // flag = true; // try { // class DummyClass {} // } catch (err) { // flag = false; // } navigator.supports.wp = { // classes: flag, customElements: 'customElements' in window, import: 'import' in document.createElement('link'), shadowDOM: !!HTMLElement.prototype.attachShadow, template: 'content' in document.createElement('template') }; } return navigator.supports.wp; } }, getScroll: { configurable: true, enumerable: true, value: () => { let x, y; x = (self.pageXOffset) ? self.pageXOffset : (document.documentElement && document.documentElement.scrollLeft) ? document.documentElement.scrollLeft : document.body.scrollLeft; y = (self.pageYOffset) ? self.pageYOffset : (document.documentElement && document.documentElement.scrollTop) ? document.documentElement.scrollTop : document.body.scrollTop; return { x, y }; } }, getPosition: { configurable: true, enumerable: true, value: (element) => { let x, y; x = 0; y = 0; while (element != null) { x += element.offsetLeft; y += element.offsetTop; element = element.offsetParent; } return { x, y }; } }, getPageSize: { configurable: true, enumerable: true, value: function() { let xScroll, yScroll, width, height; const { width:winWidth, height:winHeight } = this.getViewportSize(); if (window.innerHeight && window.scrollMaxY) { xScroll = document.body.scrollWidth; yScroll = window.innerHeight + window.scrollMaxY; } else if (document.body.scrollHeight > document.body.offsetHeight) { xScroll = document.body.scrollWidth; yScroll = document.body.scrollHeight; } else { xScroll = document.body.offsetWidth; yScroll = document.body.offsetHeight; } width = (xScroll < winWidth) ? winWidth : xScroll; height = (yScroll < winHeight) ? winHeight : yScroll; return { width, height }; } }, getViewportSize: { configurable: true, enumerable: true, value: () => { let width, height; if (self.innerHeight) { width = self.innerWidth; height = self.innerHeight; } else if (document.documentElement && document.documentElement.clientHeight) { width = document.documentElement.clientWidth; height = document.documentElement.clientHeight; } else if (document.body) { width = document.body.clientWidth; height = document.body.clientHeight; } return { width, height }; } }, getSize: { configurable: true, enumerable: true, value: (element) => { let width, height; width = element.offsetWidth; height = element.offsetHeight; return { width, height }; } }, getRand: { configurable: true, enumerable: true, value: (a, b) => { let min, max; if (a > b) { min = a; max = b; } else { min = b; max = a; } return Math.floor(Math.random() * (max - min + 1) + min); } }, getRandomIntInclusive: { configurable: true, enumerable: true, value: (min, max) => { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } }, grabStyleSheet: { configurable: true, enumerable: true, value: function() { return navigator.customStyleSheet || (function() { navigator.customStyleSheet = document.createElement('style'); document.head.appendChild(navigator.customStyleSheet); return navigator.customStyleSheet; })(); } }, removeAllChildNodes: { configurable: true, enumerable: true, value: function(parent) { while (parent.firstChild) { parent.removeChild(parent.firstChild); } } }, isScrollbarShow: { configurable: true, enumerable: true, value: function() { /* https://davidwalsh.name/detect-scrollbar-width */ const scrollDiv = document.createElement('div'); scrollDiv.style.cssText = 'inline-size:100px;block-size:100px;overflow:scroll;position:absolute;top:-9999px;'; document.body.appendChild(scrollDiv); const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; scrollDiv.remove(); return scrollbarWidth > 0; } }, isObjectsEqual: { configurable: true, enumerable: true, value: function(obj1, obj2) { const obj1Keys = Object.keys(obj1).sort(); const obj2Keys = Object.keys(obj2).sort(); let objEqual = false; if (obj1Keys.length !== obj2Keys.length) { objEqual = false; } else { objEqual = obj1Keys.every((key, index) => { const objValue1 = obj1[key]; const objValue2 = obj2[obj2Keys[index]]; return objValue1 === objValue2; }); } return objEqual; } }, isValidURL: { configurable: true, enumerable: true, value: function(url) { try { return Boolean(new URL(url)); } catch(e){ return false; } } }, loadImage: { configurable: true, enumerable: true, value: function(url, crossOrigin = '') { return new Promise( (resolve, reject) => { const img = new Image(); img.onload = () => { resolve(img); }; img.onerror = (e) => { reject(e); }; if (!!crossOrigin) { img.crossOrigin = crossOrigin; } img.src = url; } ); } }, prepareFetch: { configurable: true, enumerable: true, value: function(timeout = 5000) { const fetchController = new AbortController(); const signal = fetchController.signal; // timeout setTimeout(() => fetchController.abort(), timeout); return signal; } }, cloneStyleSheetsToDocument: { configurable: true, enumerable: true, value: function(targetDocument) { [...document.styleSheets].forEach((styleSheet) => { try { const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join(''); const style = document.createElement('style'); style.textContent = cssRules; targetDocument.head.appendChild(style); } catch (e) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.type = styleSheet.type; link.media = styleSheet.media; link.href = styleSheet.href; targetDocument.head.appendChild(link); } }); } }, addStylesheetRules: { configurable: true, enumerable: true, value: function(selector = '', props = {}, styleSheet = this.grabStyleSheet()) { /** * Add a stylesheet rule to the document * @example addStylesheetRules( 'body', { background: '#f00', color: '#0f0' } [, styleSheet] ) addStylesheetRules( '@keyframes fancy-anchor-ripple', { '0%': '{transform:scale(1);opacity:1;}', '100%': '{transform:scale(100);opacity:0;}' } [, styleSheet] ) */ if (!selector || !Object.keys(props).length || !styleSheet.sheet) return; let propStr, findIndex, sign; sign = (/keyframes/i.test(selector)) ? '' : ';'; styleSheet = styleSheet.sheet; propStr = Object.keys(props).reduce( (acc, cur) => { let sign; sign = /^\{.*\}$/.test(props[cur]) ? '' : ':'; return acc.concat([`${cur}${sign}${props[cur]}`]); } , []).join(sign); findIndex = Array.from(styleSheet.cssRules).findIndex((rule) => rule.selectorText == selector); if (findIndex !== -1) { try { styleSheet.cssRules[findIndex].style.cssText = propStr; } catch(err) { /*error*/ } } else { try { styleSheet.insertRule(`${selector}{${propStr}}`, styleSheet.cssRules.length); } catch(err) { /*error*/ } } } } // addStylesheetRules: { // configurable: true, // enumerable: true, // value: function(selector = '', props = {}, ...others) { // /** // * Add a stylesheet rule to the document // * @example // addStylesheetRules( // 'body', // { // background: '#f00', // color: '#0f0' // } // [, styleSheet] // ) // addStylesheetRules( // '@keyframes fancy-anchor-ripple', // { // '0%': '{transform:scale(1);opacity:1;}', // '100%': '{transform:scale(100);opacity:0;}' // } // [, styleSheet] // ) // addStylesheetRules( // 'body', // { // background: '#f00', // color: '#0f0' // } // 'components.heros' // [, styleSheet] // ) // */ // const [ // layerName = undefined, // styleSheet = this.grabStyleSheet() // ] = others; // const sheet = styleSheet.sheet; // const propStr = Object.keys(props).reduce( // (acc, cur) => { // let sign; // sign = /^\{.*\}$/.test(props[cur]) ? '' : ':'; // return acc.concat([`${cur}${sign}${props[cur]}`]); // } // , []).join((/keyframes/i.test(selector)) ? '' : ';'); // if (layerName) { // // add rules to layer // const layer = Array.from(sheet.cssRules).find((rule) => rule?.name === layerName); // if (layer !== undefined) { // try { // const idx = Array.from(layer.cssRules).findIndex((rule) => rule.selectorText === selector); // layer.cssRules[idx].style.cssText = propStr; // } catch(err) { /*error*/ } // } else { // try { // sheet.insertRule(`@layer ${layerName}{${selector}{${propStr}}}`, sheet.cssRules.length); // } catch(err) { /*error*/ } // } // } else { // const idx = Array.from(sheet.cssRules).findIndex((rule) => rule.selectorText === selector); // if (idx !== -1) { // try { // sheet.cssRules[idx].style.cssText = propStr; // } catch(err) { /*error*/ } // } else { // try { // sheet.insertRule(`${selector}{${propStr}}`, sheet.cssRules.length); // } catch(err) { /*error*/ } // } // } // } // } });