`);\r\n $dotWrapper.append($dot);\r\n $dot.on(\"click\", (event:Event)=>\r\n {\r\n event.preventDefault();\r\n this.changeIndex(index);\r\n });\r\n });\r\n }\r\n }\r\n\r\n changeIndex(index: number)\r\n {\r\n if (this.isImageOnLoading) return;\r\n if (!this.dataList) return;\r\n if (index < 0) return;\r\n if (index >= this.dataList.length) return;\r\n if(index === this.currentIndex) return;\r\n\r\n this.currentIndex = index;\r\n\r\n this.$btnPrev[0].setAttribute(\"disabled\", (index === 0) ? \"true\" : \"false\");\r\n this.$btnNext[0].setAttribute(\"disabled\", (index >= (this.dataList.length - 1)) ? \"true\" : \"false\");\r\n\r\n if(this.dataList.length > 1)\r\n {\r\n let $dots = this.$root.find(\".dot\").attr(\"focused\", \"false\");\r\n $dots[index].setAttribute(\"focused\", \"true\");\r\n }\r\n\r\n this.changeImage();\r\n }\r\n\r\n changeImage()\r\n {\r\n gsap.killTweensOf(this.$image);\r\n gsap.set(this.$image, { opacity: 0 });\r\n\r\n this.isImageOnLoading = true;\r\n\r\n let img = this.img = new Image();\r\n img.onload = ()=>\r\n {\r\n this.isImageOnLoading = false;\r\n this.$image.css(\"background-image\", \"url(\" + img.src + \")\");\r\n this.updateSize();\r\n\r\n };\r\n\r\n img.onerror = ()=>\r\n {\r\n this.isImageOnLoading = false;\r\n this.img = null;\r\n }\r\n\r\n img.src = this.dataList[this.currentIndex][\"image_src\"];\r\n\r\n ViewportRoot.emitter.addListener(\"updated\", ()=>\r\n {\r\n if(!this.isOnStage) return;\r\n this.updateSize();\r\n });\r\n }\r\n\r\n updateSize()\r\n {\r\n if (this.isImageOnLoading || !this.img) return;\r\n\r\n let bleedX = 70,\r\n bleedY = 40,\r\n maxWidth = ViewportRoot.width - bleedX * 2,\r\n maxHeight = ViewportRoot.height - bleedY * 2,\r\n bound = Geom.caculateContain(maxWidth, maxHeight, this.img.width, this.img.height);\r\n\r\n if (bound.ratio > 1) {\r\n bound.width = this.img.width;\r\n bound.height = this.img.height;\r\n }\r\n\r\n gsap.to(this.$content, { duration: .25, width: bound.width, height: bound.height });\r\n gsap.to(this.$image, { duration: .25, opacity: 1, delay: .25 });\r\n\r\n }\r\n}","import ComponentBase from \"@components/ComponentBase\";\r\nimport { ComponentElem } from \"@components/interfaces\";\r\n\r\nexport default class SectionQA extends ComponentBase\r\n{\r\n private _sendGtagEvents: boolean = false;\r\n private _gtagEventName: string = \"\";\r\n private _gtagEventLabel: string = \"\";\r\n\r\n\r\n constructor(elem: ComponentElem)\r\n {\r\n super(elem);\r\n\r\n if(this.$root.attr(\"send-gtag_events\") === \"true\")\r\n {\r\n this._sendGtagEvents = true;\r\n this._gtagEventName = this.$root.attr(\"event_name\");\r\n this._gtagEventLabel = this.$root.attr(\"event_label\");\r\n }\r\n\r\n this._setupQuestions();\r\n }\r\n\r\n private _setupQuestions()\r\n {\r\n let $questionList = this.$root.find(\".question\");\r\n $questionList.each((index, elem)=>\r\n {\r\n let $question = $(elem),\r\n askIndex = $question.find(\".ask\").attr(\"index\"),\r\n isOpen = elem.getAttribute(\"is-open\") === \"true\",\r\n $ask = $question.find(\".ask\"),\r\n $answer = $question.find(\".answer\"),\r\n $answerWrapper = $answer.find(\".wrapper\");\r\n\r\n $ask.on(\"click\", (event:JQuery.ClickEvent)=>\r\n {\r\n event.preventDefault();\r\n isOpen = !isOpen;\r\n \r\n if(this._sendGtagEvents)\r\n {\r\n window.gtag(\"event\", this._gtagEventName + askIndex, {event_label: this._gtagEventLabel + askIndex});\r\n }\r\n \r\n update();\r\n });\r\n\r\n update();\r\n\r\n function update() {\r\n \r\n elem.setAttribute(\"is-open\", isOpen? \"true\": \"false\");\r\n if(isOpen)\r\n {\r\n let maxHeight = $answerWrapper.height();\r\n $answer.css(\"max-height\", maxHeight);\r\n }\r\n else\r\n {\r\n $answer.css(\"max-height\", \"0\");\r\n }\r\n }\r\n \r\n });\r\n }\r\n}","import ComponentBase from \"@components/ComponentBase\";\r\nimport { ComponentElem } from \"@components/interfaces\";\r\nimport { log } from \"console\";\r\nimport { Viewport } from \"../../ts/sframe/Viewport\";\r\nimport ViewportRoot from \"../../ts/sframe/ViewportRoot\";\r\n\r\nlet _isShoppingOpen = false;\r\n\r\nexport default class ShoppingPanel extends ComponentBase\r\n{\r\n $shoppingContent: JQuery;\r\n $shoppingTrigger: JQuery;\r\n\r\n private _isShoppingOpen: boolean;\r\n\r\n static instance: ShoppingPanel;\r\n\r\n constructor(elem: ComponentElem)\r\n {\r\n super(elem);\r\n\r\n this.$shoppingContent = this.$root.find(\".content\");\r\n\r\n this.$shoppingTrigger = this.$root.find(\".trigger\").on(\"click\", (event: Event) =>\r\n {\r\n event.preventDefault();\r\n\r\n _isShoppingOpen = !_isShoppingOpen;\r\n this.$root.attr(\"is-open\", _isShoppingOpen ? \"true\" : \"false\");\r\n this._resizeShopping();\r\n });\r\n\r\n // ViewportRoot.emitter.on(\"updated\", () =>\r\n // {\r\n // this._resizeShopping();\r\n // })\r\n\r\n ShoppingPanel.instance = this;\r\n\r\n }\r\n\r\n resize(scale: number)\r\n {\r\n \r\n\r\n if (scale === 1) {\r\n this.$shoppingTrigger.css\r\n ({\r\n \"transform\": \"\",\r\n \"margin-right\": \"\"\r\n });\r\n }\r\n else {\r\n this.$shoppingTrigger.css\r\n ({\r\n \"transform\": \"scale(\" + scale + \")\",\r\n // \"margin-right\": -(1 - triggerScale) * 90\r\n });\r\n }\r\n }\r\n\r\n\r\n private _resizeShopping()\r\n {\r\n let vp = ViewportRoot;\r\n\r\n let scale = 1,\r\n triggerScale = 1;\r\n\r\n const thehold1 = 460,\r\n thehold2 = 960;\r\n\r\n if (vp.width <= thehold1) {\r\n scale = vp.width / thehold1;\r\n triggerScale = vp.width / thehold2;\r\n }\r\n else if (vp.width <= thehold2) {\r\n triggerScale = vp.width / thehold2;\r\n }\r\n\r\n if (scale === 1) {\r\n this.$shoppingContent.css\r\n ({\r\n \"transform\": \"\"\r\n });\r\n\r\n this.$shoppingTrigger.css\r\n ({\r\n \"left\": \"\",\r\n });\r\n }\r\n else {\r\n this.$shoppingContent.css\r\n ({\r\n \"transform\": \"scale(\" + scale + \")\"\r\n });\r\n\r\n if (_isShoppingOpen) {\r\n this.$shoppingTrigger.css\r\n ({\r\n \"left\": (thehold1 - vp.width),\r\n });\r\n }\r\n else {\r\n this.$shoppingTrigger.css\r\n ({\r\n \"left\": \"\",\r\n });\r\n }\r\n }\r\n\r\n // if (triggerScale === 1) {\r\n // this.$shoppingTrigger.css\r\n // ({\r\n // \"transform\": \"\",\r\n // \"margin-right\": \"\"\r\n // });\r\n // }\r\n // else {\r\n // this.$shoppingTrigger.css\r\n // ({\r\n // \"transform\": \"scale(\" + triggerScale + \")\",\r\n // // \"margin-right\": -(1 - triggerScale) * 90\r\n // });\r\n // }\r\n }\r\n}","import ComponentBase from \"@components/ComponentBase\";\r\nimport { ComponentElem } from \"@components/interfaces\";\r\n\r\nexport default class StickBtnBuy extends ComponentBase\r\n{\r\n static instance: StickBtnBuy;\r\n\r\n constructor(elem: ComponentElem)\r\n {\r\n super(elem);\r\n StickBtnBuy.instance = this; \r\n }\r\n\r\n resize(scale: number = 1)\r\n {\r\n if(scale === 1)\r\n {\r\n this.$root.css({\r\n \"transform\": \"\"\r\n })\r\n }\r\n else\r\n {\r\n this.$root.css({\r\n \"transform\": `scale(${scale})`\r\n })\r\n\r\n }\r\n }\r\n}","import { ComponentElem } from \"@components/interfaces\";\r\nimport PopupBase from \"./PopupBase\";\r\nimport Popups from \"./Popups\";\r\nimport { gsap } from \"gsap\";\r\n\r\n// const TEXT_LINE_HEIGHT_PC = 26,\r\n// TEXT_LINE_HEIGHT_MOBILE = 32;\r\n\r\nconst TEXT_THEHOLD = 38;\r\n\r\ntype MyOptions =\r\n {\r\n complexTitle?: string,\r\n title?: string,\r\n text?: string,\r\n mode?: string,\r\n cancelLabel?: string | false,\r\n confirmLabel?: string,\r\n autoClose?: boolean,\r\n closeByCover?: boolean,\r\n closeByX?: boolean,\r\n resolveAtStart?: boolean,\r\n theme?: \"default\" | \"eula\" | \"title-with-line\"\r\n }\r\n\r\nlet $pending = $(`
`),\r\n _isPending = false;\r\n\r\nexport default class EZAlert extends PopupBase\r\n{\r\n static instance: EZAlert;\r\n\r\n autoClose_cover: boolean = false;\r\n\r\n static pending(text: string | boolean = \"請稍候\", cb?: Function)\r\n {\r\n if (text === false) {\r\n if (!_isPending) return;\r\n _isPending = false;\r\n gsap.killTweensOf($pending);\r\n gsap.to($pending, {\r\n duration: .05, opacity: 0, onComplete: () =>\r\n {\r\n $pending.detach();\r\n if (cb) cb();\r\n }\r\n });\r\n }\r\n else {\r\n if (_isPending) return;\r\n _isPending = true;\r\n $pending.find(\".text\").text(text);\r\n $(`body`).append($pending);\r\n gsap.killTweensOf($pending);\r\n gsap.set($pending, { opacity: 0 });\r\n gsap.to($pending, {\r\n duration: .2, opacity: 1, onComplete: () =>\r\n {\r\n if (cb) cb();\r\n }\r\n });\r\n\r\n }\r\n }\r\n\r\n /**\r\n * \r\n * @param title 標題\r\n * @param text 內文\r\n * @param autoClose 是否自動執行關閉(當關閉後需要呼叫另一個 popup 時, 設定為 false 避免背景黑底閃爍)\r\n * @param resolveAtStart 決定 promise 在關閉前還是關閉後 resolve\r\n * @param closePending 自動關閉 pending 狀態\r\n * @returns \r\n */\r\n static alert(title?: string, text?: string, autoClose: boolean = true, resolveAtStart: boolean = true, closePending: boolean = true)\r\n {\r\n if (closePending) this.pending(false);\r\n return this.instance.alert(title, text, undefined, autoClose, resolveAtStart);\r\n }\r\n\r\n static alertJSON(title: string, jsonObj: any)\r\n {\r\n\r\n let text = JSON.stringify(jsonObj, undefined, 2);\r\n text = `
${text}
`;\r\n\r\n return EZAlert.alert(title, text);\r\n }\r\n\r\n constructor(elem: ComponentElem)\r\n {\r\n super(elem);\r\n EZAlert.instance = this;\r\n\r\n this.$root.find(\".btn-close\").on(\"click\", (event:JQuery.ClickEvent)=>\r\n {\r\n event.preventDefault();\r\n \r\n Popups.close();\r\n });\r\n\r\n window.EZ.alert = (title: string, text: string) =>\r\n {\r\n return EZAlert.alert(title, text);\r\n };\r\n\r\n window.EZ.alertJSON = (title: string, obj: any) =>\r\n {\r\n return EZAlert.alertJSON(title, obj);\r\n };\r\n }\r\n\r\n show(options: MyOptions)\r\n {\r\n let complexTitle = options.complexTitle,\r\n title = options.title,\r\n text = options.text;\r\n\r\n if (!title && !text) throw new Error(`title 和 text 不能皆為空值`);\r\n\r\n options.theme = options.theme || \"default\";\r\n options.closeByCover = options.closeByCover === true? true: false;\r\n\r\n this.autoClose_cover = options.closeByCover;\r\n\r\n this.$root.find(\".btn-close\").css(\"display\", options.closeByX === true? \"block\": \"none\");\r\n \r\n\r\n let detailMode = \"all\";\r\n if (!title) detailMode = \"text-only\";\r\n if (!text) detailMode = \"title-only\";\r\n if (complexTitle) detailMode = \"complex\";\r\n\r\n this.$root.attr(\"detail-mode\", detailMode);\r\n this.$root.attr(\"mode\", options.mode);\r\n\r\n\r\n this.$root.attr(\"theme\", options.theme);\r\n\r\n this.$root.find(\".btn-cancel\").text(options.cancelLabel).css(\"display\", options.cancelLabel === false? \"none\": \"\");\r\n this.$root.find(\".btn-confirm\").text(options.confirmLabel);\r\n\r\n let noButtons = (options.mode === \"alert\" && options.cancelLabel === false);\r\n this.$root.find(\".buttons\").css(\"display\", noButtons? \"none\": \"\");\r\n\r\n let $text = this.$root.find(\".text\"),\r\n $title = this.$root.find(\".title\"),\r\n $complexTitle = this.$root.find(\".complex-title\");\r\n\r\n $title.text(title ? title : \"\");\r\n $text[0].innerHTML = (text ? text : \"\");\r\n\r\n $complexTitle.text(complexTitle? complexTitle: \"\");\r\n\r\n let thehold = detailMode === \"complex\"? 38: TEXT_THEHOLD;\r\n\r\n if (text && options.theme === \"default\") {\r\n this.$root.css(\"visibility\", \"hidden\");\r\n $(\"#popups\").append(this.$root); \r\n \r\n let isSingleLine = $text.height() <= thehold;\r\n this.$root.css(\"visibility\", \"\").detach();\r\n\r\n $text.css(\"text-align\", isSingleLine ? \"center\" : \"\");\r\n }\r\n else {\r\n $text.css(\"text-align\", \"\");\r\n }\r\n\r\n Popups.to(\"ez-alert\");\r\n\r\n return new Promise((resolve) =>\r\n {\r\n this.$root.find(\".btn-cancel\").off().on(\"click\", (event: Event) =>\r\n {\r\n event.preventDefault();\r\n\r\n if (options.autoClose) {\r\n if (options.resolveAtStart) {\r\n Popups.close();\r\n resolve(false);\r\n }\r\n else {\r\n Popups.close().then(()=>\r\n {\r\n resolve(false);\r\n });\r\n }\r\n }\r\n else {\r\n resolve(false);\r\n }\r\n });\r\n\r\n this.$root.find(\".btn-confirm\").off().on(\"click\", (event: Event) =>\r\n {\r\n event.preventDefault();\r\n\r\n if (options.autoClose) {\r\n if (options.resolveAtStart) {\r\n Popups.close();\r\n resolve(true);\r\n }\r\n else {\r\n Popups.close().then(()=>\r\n {\r\n resolve(true);\r\n });\r\n }\r\n }\r\n else {\r\n resolve(true);\r\n }\r\n });\r\n\r\n });\r\n }\r\n\r\n confirm(title?: string, text?: string, cancelLabel: string | false = \"取消\", confirmLabel: string = \"確認\", autoClose: boolean = true, resolveAtStart: boolean = true, options?: MyOptions)\r\n {\r\n options = Object.assign({}, { mode: \"confirm\", cancelLabel: cancelLabel, confirmLabel: confirmLabel, autoClose: autoClose, resolveAtStart: resolveAtStart, title: title, text: text }, options);\r\n return this.show(options);\r\n }\r\n\r\n alert(title?: string, text?: string, cancelLabel: string | false = \"OK\", autoClose: boolean = true, resolveAtStart: boolean = true, options?: MyOptions)\r\n {\r\n options = Object.assign({}, { mode: \"alert\", cancelLabel: cancelLabel, autoClose: autoClose, resolveAtStart: resolveAtStart, title: title, text: text }, options);\r\n return this.show(options);\r\n }\r\n\r\n complexConfirm(complexTitle: string, title?: string, text?: string, cancelLabel: string = \"取消\", confirmLabel: string = \"確認\", options?: MyOptions)\r\n {\r\n let outputOptions: MyOptions = {\r\n mode: \"confirm\",\r\n title: title,\r\n text: text,\r\n complexTitle: complexTitle,\r\n cancelLabel: cancelLabel,\r\n confirmLabel: confirmLabel,\r\n autoClose: true,\r\n resolveAtStart: true\r\n };\r\n\r\n outputOptions = Object.assign({}, outputOptions, options);\r\n\r\n return this.show(outputOptions);\r\n }\r\n}","import { Common } from \"@components/common/CommonTypes\";\r\nimport ComponentBase from \"@components/ComponentBase\";\r\nimport { ComponentElem } from \"@components/interfaces\";\r\nimport Popups from \"./Popups\";\r\n\r\nexport default class PopupBase extends ComponentBase\r\n{\r\n popupId: string = \"\";\r\n\r\n isOnStage: boolean = false;\r\n\r\n autoClose_popup: boolean = false;\r\n autoClose_menu: boolean = true;\r\n autoClose_cover: boolean = true;\r\n\r\n coverColor: string = `rgba(6, 7, 7, 0.5)`;\r\n\r\n popupAlign: Common.Align = 5;\r\n\r\n $root: JQuery;\r\n\r\n constructor(elem: ComponentElem)\r\n {\r\n super(elem);\r\n this.$root.detach();\r\n }\r\n\r\n toMe()\r\n {\r\n window._ez_popup_to_(this.popupId);\r\n }\r\n\r\n setMinWidth?: {(width: number): void};\r\n clearDropdownSetting?: {(): void};\r\n}","import { ComponentElem } from \"@components/interfaces\";\r\nimport ViewportRoot from \"@ts/sframe/ViewportRoot\";\r\nimport gsap from \"gsap\";\r\nimport Pages from \"../common/PageRoot\";\r\nimport { PopupDefineDic, PopupDefine } from \"@components/interfaces\";\r\nimport PhotoViewer from \"./PhotoViewer\";\r\n\r\nlet $root: JQuery,\r\n $cover: JQuery,\r\n $dropdownMenus: JQuery,\r\n $dropdownMenusCover: JQuery,\r\n _isLocking: boolean = false,\r\n _currentPopup: string = \"\",\r\n _currentMenu: string = \"\",\r\n _setBackPopup: string = undefined;\r\n\r\n\r\nlet _popupDic: PopupDefineDic =\r\n{\r\n};\r\n\r\ntype MenuOptions = {\r\n position?: \"absolute\" | \"sticky\" | \"popup\",\r\n align?: \"left\" | \"center\" | \"right\"\r\n offsetTop?: number,\r\n offsetLeft?: number\r\n};\r\n\r\nlet _popupTl: any;\r\n\r\nclass Popups\r\n{\r\n static init()\r\n {\r\n window._ez_popup_to_ = (id: string) =>\r\n {\r\n Popups.to(id);\r\n };\r\n\r\n this.registComponents({\r\n // \"photo-viewer\": PhotoViewer,\r\n });\r\n\r\n $root = $(\"#popups\");\r\n $cover = $root.find(\".cover\").on(\"click\", (event: Event) =>\r\n {\r\n event.preventDefault();\r\n if (_currentPopup && _popupDic[_currentPopup].instance.autoClose_cover) {\r\n this.close();\r\n }\r\n });\r\n\r\n updateActive();\r\n\r\n for (const key in _popupDic) {\r\n if (Object.prototype.hasOwnProperty.call(_popupDic, key)) {\r\n let obj: PopupDefine = _popupDic[key],\r\n elem = document.querySelector(`[popup-id=\"${key}\"]`) as ComponentElem;\r\n if (!elem) throw new Error(`popup element for [${key}] is not exist\\n(可能是未在頁面中加入對應的模板)`);\r\n\r\n // console.log(key);\r\n // console.log(obj.componentClass);\r\n\r\n let componentClass: any = obj.componentClass? obj.componentClass: obj;\r\n obj.instance = new componentClass(elem);\r\n obj.instance.popupId = key;\r\n\r\n }\r\n }\r\n\r\n $dropdownMenus = $(\"#dropdown-menus\");\r\n $dropdownMenusCover = $dropdownMenus.find(\".cover\").on(\"click\", (event: Event) =>\r\n {\r\n event.preventDefault();\r\n this.closeMenu();\r\n });\r\n\r\n ViewportRoot.emitter.addListener(\"updated\", () =>\r\n {\r\n if (ViewportRoot.isMajorChange) onMajorViewportChange();\r\n });\r\n\r\n $root.attr(\"init\", \"true\");\r\n\r\n $(\"#shared-popups\").detach();\r\n }\r\n\r\n static getInstance(popupName: string)\r\n {\r\n if(!_popupDic[popupName]) return null;\r\n return _popupDic[popupName].instance;\r\n }\r\n\r\n static to(targetPopup: string = \"\", setBackPopup?: string)\r\n {\r\n // if (_isLocking) return;\r\n if (_isLocking && _popupTl) _popupTl.progress(1);\r\n\r\n if (targetPopup === _currentPopup) return\r\n if (!_popupDic[targetPopup] && targetPopup !== \"\") throw new Error(`popup [${targetPopup}] 不存在`);\r\n\r\n return new Promise((resolve) =>\r\n {\r\n _isLocking = true;\r\n\r\n _setBackPopup = setBackPopup;\r\n\r\n const duration = .15,\r\n ease = \"power2.out\",\r\n offset = 50;\r\n\r\n let oldPopup = _currentPopup\r\n _currentPopup = targetPopup;\r\n\r\n let tl = _popupTl = gsap.timeline();\r\n\r\n if (oldPopup === \"\") updateActive();\r\n\r\n if (oldPopup === \"\") {\r\n tl.to($cover, { duration: .2, opacity: 1 }, 0);\r\n }\r\n\r\n if (oldPopup !== \"\") {\r\n let obj: PopupDefine = _popupDic[oldPopup]; \r\n\r\n if (obj.instance.popupAlign === 4) tl.to(obj.instance.$root, { duration: duration, x: -offset, opacity: 0, ease: ease }, 0);\r\n else if (obj.instance.popupAlign === 8) tl.to(obj.instance.$root, { duration: duration, y: offset, opacity: 0, ease: ease }, 0);\r\n else tl.to(obj.instance.$root, { duration: duration, y: -offset, opacity: 0, ease: ease }, 0);\r\n\r\n tl.add(() =>\r\n {\r\n obj.instance.isOnStage = false;\r\n obj.instance.$root.attr(\"style\", \"\").detach();\r\n });\r\n } \r\n\r\n if (_currentPopup !== \"\") {\r\n let obj: PopupDefine = _popupDic[_currentPopup];\r\n\r\n tl.add(() =>\r\n {\r\n $cover.css(\"background-color\", obj.instance.coverColor);\r\n \r\n $root.attr(\"content-align\", obj.instance.popupAlign);\r\n obj.instance.isOnStage = true;\r\n $root.append(obj.instance.$root);\r\n });\r\n\r\n\r\n tl.addLabel(\"contentIn\");\r\n\r\n if (obj.instance.popupAlign === 4) {\r\n tl.set(obj.instance.$root, { duration: duration, x: -offset, opacity: 0 }, \"contentIn\");\r\n tl.to(obj.instance.$root, { duration: duration, x: 0, opacity: 1, ease: ease }, \"contentIn\");\r\n }\r\n else {\r\n tl.set(obj.instance.$root, { duration: duration, y: offset, opacity: 0 }, \"contentIn\");\r\n tl.to(obj.instance.$root, { duration: duration, y: 0, opacity: 1, ease: ease }, \"contentIn\");\r\n }\r\n }\r\n\r\n if (_currentPopup === \"\") {\r\n tl.to($cover, { duration: .2, opacity: 0 }, \"contentIn\");\r\n }\r\n\r\n tl.add(() =>\r\n {\r\n updateActive();\r\n _isLocking = false;\r\n\r\n // if(cb) cb();\r\n resolve(true);\r\n });\r\n });\r\n\r\n\r\n\r\n }\r\n\r\n static openMenu(targetPopup: string = \"\", targetElem?: HTMLElement, options?: MenuOptions)\r\n {\r\n if (targetPopup === _currentMenu) return;\r\n\r\n let defaultOptions: MenuOptions = {\r\n position: \"absolute\",\r\n align: \"center\",\r\n offsetTop: 10,\r\n offsetLeft: 0\r\n };\r\n\r\n options = {\r\n ...defaultOptions,\r\n ...options\r\n };\r\n\r\n // console.log(`target: ${targetPopup}`);\r\n // console.log(targetElem);\r\n\r\n if (targetPopup === \"\" && _currentMenu) {\r\n\r\n removeMenu();\r\n _currentMenu = \"\";\r\n }\r\n else {\r\n _currentMenu = targetPopup;\r\n\r\n\r\n if (options.position === \"sticky\") {\r\n\r\n let rect = targetElem.getBoundingClientRect(),\r\n top = rect.top + rect.height + options.offsetTop,\r\n left = rect.left + window.scrollX + options.offsetLeft;\r\n\r\n completeMenu(rect, left, top, options);\r\n }\r\n else if (options.position === \"absolute\") {\r\n\r\n let rect = targetElem.getBoundingClientRect(),\r\n top = rect.top + window.scrollY + rect.height + options.offsetTop,\r\n dx = Math.max(0, (ViewportRoot.width - ViewportRoot.getScrollbarWidth() - 1280) * .5),\r\n left = rect.left + window.scrollX - dx + options.offsetLeft;\r\n\r\n // console.log(ViewportRoot.scrollbarWidth);\r\n\r\n\r\n completeMenu(rect, left, top, options);\r\n }\r\n }\r\n }\r\n\r\n static openMenuAsPopup(targetPopup: string = \"\")\r\n {\r\n if (targetPopup === _currentMenu) return;\r\n _currentMenu = targetPopup;\r\n\r\n $dropdownMenus.attr(\"active\", \"true\");\r\n\r\n let obj = _popupDic[_currentMenu],\r\n $popup = obj.instance.$root;\r\n\r\n // $popup.attr(\"mode\", \"menu\");\r\n\r\n\r\n let $cover = $dropdownMenus.find(\".cover\");\r\n\r\n $dropdownMenus.attr(\"position\", \"popup\").append($popup);\r\n\r\n let tl = gsap.timeline();\r\n tl.set($dropdownMenus, { opacity: 1 });\r\n tl.set($cover, { opacity: 0 });\r\n tl.set($popup, { y: 40 });\r\n tl.to($cover, { duration: .15, opacity: 1 });\r\n tl.to($popup, { duration: .2, y: 0 }, 0);\r\n\r\n // gsap.from($cover, { duration: .15, opacity: 0 });\r\n // gsap.from($popup, { duration: .15, y: 20, delay: .1 });\r\n }\r\n\r\n static close()\r\n {\r\n if (_setBackPopup) {\r\n let string = _setBackPopup;\r\n _setBackPopup = undefined;\r\n return this.to(string);\r\n }\r\n else {\r\n return this.to(\"\");\r\n }\r\n }\r\n\r\n static closeMenu()\r\n {\r\n this.openMenu(\"\");\r\n }\r\n\r\n static registComponents(dic: PopupDefineDic)\r\n {\r\n _popupDic = Object.assign({}, dic, _popupDic);\r\n }\r\n}\r\n\r\nfunction onMajorViewportChange()\r\n{\r\n if (_currentMenu) {\r\n let obj = _popupDic[_currentMenu];\r\n if (obj.instance.autoClose_menu) Popups.closeMenu();\r\n }\r\n\r\n if (_currentPopup) {\r\n let obj = _popupDic[_currentPopup];\r\n if (obj.instance.autoClose_popup) Popups.close();\r\n }\r\n}\r\n\r\nfunction completeMenu(rect: DOMRect, left: number, top: number, options: MenuOptions): void\r\n{\r\n $dropdownMenus.attr(\"active\", \"true\");\r\n\r\n let obj = _popupDic[_currentMenu],\r\n $popup = obj.instance.$root;\r\n if (obj.instance.setMinWidth) obj.instance.setMinWidth(rect.width);\r\n\r\n $popup.attr(\"mode\", \"menu\");\r\n\r\n $dropdownMenus.attr(\"position\", options.position).append($popup)\r\n\r\n let width = $popup.width();\r\n\r\n\r\n switch (options.align) {\r\n\r\n case \"center\":\r\n left = left + rect.width * .5 - width * .5;\r\n break;\r\n\r\n case \"right\":\r\n left = left + rect.width - width;\r\n break;\r\n }\r\n\r\n // gsap.from($popup, {duration: .15, transformOrigin: \"center top\", scaleX: \".8\", ease: \"power1.out\"});\r\n gsap.set($dropdownMenus, { opacity: 1 });\r\n gsap.from($popup, { duration: .15, opacity: 0, ease: \"power1.out\" });\r\n\r\n if (options.position === \"sticky\") {\r\n let vpWidth = Math.max(ViewportRoot.width, 1440);\r\n\r\n $popup.css({\r\n \"left\": \"50%\",\r\n \"margin-left\": left - vpWidth * .5,\r\n \"top\": top + \"px\"\r\n });\r\n }\r\n else {\r\n $popup.css({\r\n \"left\": left + \"px\",\r\n \"top\": top + \"px\"\r\n });\r\n }\r\n}\r\n\r\nfunction removeMenu()\r\n{\r\n\r\n let obj = _popupDic[_currentMenu];\r\n\r\n // let tl = gsap.timeline();\r\n\r\n // tl.to($dropdownMenus, { duration: .15, opacity: 0 });\r\n\r\n // tl.add(() =>\r\n // {\r\n\r\n $dropdownMenus.attr(\"active\", \"false\").attr(\"position\", \"\");\r\n\r\n\r\n if (obj) {\r\n if (obj.instance.clearDropdownSetting) obj.instance.clearDropdownSetting();\r\n\r\n obj.instance.$root.attr(\"style\", \"\").detach().attr(\"mode\", \"popup\");\r\n }\r\n\r\n // });\r\n}\r\n\r\nfunction updateActive()\r\n{\r\n let isActive = _currentPopup !== \"\";\r\n $root.attr(\"active\", isActive ? \"true\" : \"false\");\r\n Pages.toggleScrollLock(isActive);\r\n}\r\n\r\nexport default Popups;","// import error_schema from \"@schema/error.json\";\r\n// import response_message_schema from \"@schema/response-message.json\";\r\n// import phone_schema from \"@schema/phone.json\";\r\n// import country_schema from \"@schema/country.json\";\r\n// import address_schema from \"@schema/address.json\";\r\n// import gender_schema from \"@schema/gender.json\";\r\n// import paging_info_schema from \"@schema/paging-info.json\";\r\n// import next_url_schema from \"@schema/next_url.json\";\r\n// import coupon_schema from \"@schema/coupon.json\";\r\n// import basic_info from \"@schema/basic-info.json\";\r\n// import passenger_info from \"@schema/passenger-info.json\";\r\n// import platform_schema from \"@schema/platform.json\";\r\n// import photo_schema from \"@schema/photo.json\";\r\n\r\nimport Ajv, { JSONSchemaType } from \"ajv\";\r\nimport addFormats from \"ajv-formats\";\r\n\r\n\r\nlet _ajv: Ajv;\r\n\r\nlet AjvProxy =\r\n{\r\n init()\r\n {\r\n _ajv = new Ajv({ coerceTypes: \"array\" });\r\n // _ajv.addSchema(error_schema);\r\n\r\n addFormats(_ajv, [\"email\"]);\r\n addFormats(_ajv, [\"date\"]);\r\n\r\n // AjvProxy.addSchema(error_schema);\r\n // AjvProxy.addSchema(response_message_schema);\r\n // AjvProxy.addSchema(phone_schema);\r\n // AjvProxy.addSchema(country_schema);\r\n // AjvProxy.addSchema(address_schema);\r\n // AjvProxy.addSchema(gender_schema);\r\n // AjvProxy.addSchema(next_url_schema);\r\n // AjvProxy.addSchema(paging_info_schema);\r\n // AjvProxy.addSchema(coupon_schema);\r\n // AjvProxy.addSchema(basic_info);\r\n // AjvProxy.addSchema(passenger_info);\r\n // AjvProxy.addSchema(platform_schema);\r\n // AjvProxy.addSchema(photo_schema);\r\n\r\n // SchemaTest.test();\r\n },\r\n\r\n addSchema(schema: any, name?: string)\r\n {\r\n if (!name) name = schema[\"$id\"];\r\n\r\n\r\n\r\n if (!_ajv.getSchema(name)) {\r\n _ajv.addSchema(schema, name);\r\n // console.log(\"add \" + name);\r\n // console.log(schema);\r\n }\r\n },\r\n\r\n validate
(schemaJson: any, jsonData: any, coerceTypes: any = \"array\"): { data: DataType, error?: any }\r\n {\r\n _ajv.opts.coerceTypes = coerceTypes; \r\n\r\n try {\r\n\r\n let schemaId: string = schemaJson[\"$id\"];\r\n\r\n AjvProxy.addSchema(schemaJson, schemaId);\r\n let validate = _ajv.getSchema(schemaId);\r\n\r\n // console.log(validate.schema);\r\n // console.log(\"validae \" + schemaId);\r\n\r\n if (validate(jsonData)) {\r\n return { data: jsonData as DataType };\r\n }\r\n else {\r\n console.error(`JSON Schema [${schemaId}] 驗證失敗:\\n=> ` + validate.errors[0].message, { jsonData: jsonData, errors: validate.errors });\r\n console.dir(validate.errors[0]);\r\n\r\n return { error: { jsonData: jsonData, errors: validate.errors, message: `JSON Schema [${schemaId}] 驗證失敗` }, data: jsonData as DataType };\r\n }\r\n }\r\n catch (e) {\r\n console.error(e);\r\n return { error: e, data: null };\r\n }\r\n }\r\n}\r\n\r\nexport default AjvProxy;","// import { Utility } from \"./sframe/Utility\";\r\n\r\nimport { Common } from \"@components/common/CommonTypes\";\r\nimport EZAlert from \"@root/components/popups/EZAlert\";\r\n\r\nimport GS from \"./GlobalSetting\";\r\nimport AjvProxy from \"@root/ts/AjvProxy\";\r\n\r\nlet _testData: any,\r\n _apiResponseAfterfixs: any = {};\r\n\r\nlet _apiExtension = \"\",\r\n _apiPath = \"./api/\",\r\n _method = \"POST\",\r\n _alwaysCompleteWithError = true,\r\n _useTestDataIfExist = true,\r\n _simulateDelay = 1,\r\n _dataType = \"json\",\r\n _logSendingData = false;\r\n\r\nlet _token: string = \"\";\r\n\r\nexport interface ApiProxyOptions\r\n{\r\n testDataName?: string | boolean;\r\n method?: string;\r\n async?: boolean;\r\n completeWithError?: boolean;\r\n sendAsJsonText?: boolean;\r\n simDelay?: number;\r\n schema?: any;\r\n exampleIndex?: number;\r\n userPending?: boolean;\r\n}\r\n\r\nlet _fetchedPageData: any = {};\r\n\r\nlet ApiProxy = {\r\n\r\n getCommonPageData(): Common.CommonPageData\r\n {\r\n return ApiProxy.getPageData(\"common-page-data\");\r\n },\r\n\r\n // getPageData(): any\r\n // {\r\n // return ApiProxy.getDataFromHtml(\"page-data\");\r\n // },\r\n\r\n // convertPageData(id: string, converter?: any): any\r\n // {\r\n // if (_fetchedPageData[id]) return _fetchedPageData[id];\r\n // _fetchedPageData[id] = converter? converter(ApiProxy.getPageDataString(id)): JSON.parse(ApiProxy.getPageDataString(id));\r\n // return _fetchedPageData[id];\r\n // },\r\n\r\n getPageDataString(id: string): string\r\n {\r\n let elem = document.getElementById(id);\r\n if(!elem) return null;\r\n\r\n let jsonString = elem.innerHTML as string;\r\n jsonString = jsonString.replace(/\\\\\"|\"(?:\\\\\"|[^\"])*\"|(\\/\\/.*|\\/\\*[\\s\\S]*?\\*\\/)/g, (m, g) => g ? \"\" : m);\r\n return jsonString;\r\n },\r\n\r\n validatePageData(id: string = \"page-data\", schema?: any)\r\n {\r\n let string = ApiProxy.getPageDataString(id);\r\n let obj = JSON.parse(string);\r\n let result = AjvProxy.validate(schema, obj);\r\n if (!result.error) {\r\n obj = result.data;\r\n }\r\n\r\n _fetchedPageData[id] = obj;\r\n\r\n return _fetchedPageData[id];\r\n },\r\n\r\n getPageData(id: string = \"page-data\")\r\n {\r\n if (_fetchedPageData[id]) return _fetchedPageData[id];\r\n _fetchedPageData[id] = JSON.parse(ApiProxy.getPageDataString(id));\r\n\r\n // console.log(_fetchedPageData[id]);\r\n\r\n return _fetchedPageData[id];\r\n },\r\n\r\n logPageData(id: string = \"page-data\")\r\n {\r\n let data = ApiProxy.getPageData(id);\r\n data = JSON.parse(JSON.stringify(data)); // 快照物件當前狀態, 否則 console log 出的資料可能會後被後來的程式變動過\r\n console.log(data);\r\n },\r\n\r\n /**\r\n * 快速呼叫 api\r\n * @param apiName \r\n * @param params \r\n * @param autoClosePending \r\n * @param options \r\n * @returns \r\n */\r\n quickCall: (apiName: string, params: any, autoClosePending: boolean = true, options?: ApiProxyOptions): Promise =>\r\n {\r\n return new Promise((resolve) =>\r\n {\r\n EZAlert.pending(true);\r\n\r\n // let op = Object.assign({ simDelay: 800 }, options);\r\n let op = Object.assign({}, options);\r\n\r\n ApiProxy.callApi(apiName, params, op).then((result) =>\r\n {\r\n (autoClosePending) ? EZAlert.pending(false, () => { resolve(result) }) : resolve(result);\r\n // if(autoClosePending) EZAlert.pending(false);\r\n // resolve(result);\r\n\r\n }).catch((error: any) =>\r\n {\r\n console.error(error);\r\n EZAlert.pending(false);\r\n EZAlert.alert(error.message);\r\n });\r\n });\r\n },\r\n\r\n /**\r\n * 快速呼叫 api (不使用 pending 畫面)\r\n * @param apiName \r\n * @param params \r\n * @param options \r\n * @returns \r\n */\r\n quickCall2: (apiName: string, params: any, options?: ApiProxyOptions): Promise =>\r\n {\r\n return new Promise((resolve) =>\r\n {\r\n let op = Object.assign({}, options);\r\n\r\n ApiProxy.callApi(apiName, params, op).then((result) =>\r\n {\r\n resolve(result);\r\n\r\n }).catch((error: any) =>\r\n {\r\n console.error(error);\r\n EZAlert.alert(error.message);\r\n });\r\n });\r\n },\r\n\r\n callApi: function (apiName: string, params: any = {}, options: ApiProxyOptions = undefined): Promise\r\n {\r\n return new Promise((resolve, reject) =>\r\n {\r\n if (params && _logSendingData) {\r\n console.log(\"=== API[\" + apiName + \"] sending ===\");\r\n console.log(params);\r\n }\r\n\r\n options = Object.assign({ simDelay: _simulateDelay }, options);\r\n\r\n var testDataName = options.testDataName,\r\n method = options.method || _method,\r\n async = options.async === undefined ? true : options.async === undefined,\r\n completeWithError = options.completeWithError || _alwaysCompleteWithError;\r\n\r\n if (testDataName === undefined) {\r\n testDataName = _useTestDataIfExist;\r\n }\r\n\r\n if (testDataName !== false) {\r\n if (!testDataName || testDataName === true) testDataName = apiName;\r\n }\r\n\r\n if (options.schema) {\r\n let sendSchema = options.schema.properties.send;\r\n sendSchema[\"$id\"] = \"/\" + apiName + \".send\";\r\n\r\n if (options.schema.definitions) sendSchema.definitions = options.schema.definitions;\r\n\r\n let validatedResult = AjvProxy.validate(sendSchema, params, false);\r\n if (validatedResult.error) {\r\n reject(validatedResult.error);\r\n return;\r\n }\r\n } \r\n\r\n if (_testData && testDataName && _testData[testDataName]) {\r\n\r\n ApiProxy.applyTestSendData(apiName, params);\r\n\r\n var response = _testData[testDataName].response;\r\n\r\n if (!response) {\r\n console.error(\"[\" + testDataName + \"] not exist in test data\");\r\n }\r\n else {\r\n (options.simDelay === 0) ? complete(response) : setTimeout(function () { complete(response); }, options.simDelay);\r\n }\r\n }\r\n else if (GS.setting.useApiSchemaData && options.schema && options.schema.properties && options.schema.properties.response && options.schema.properties.response.examples) {\r\n\r\n let exampleIndex = options.exampleIndex || 0,\r\n response = options.schema.properties.response.examples[exampleIndex];\r\n\r\n (options.simDelay === 0) ? complete(response) : setTimeout(function () { complete(response); }, options.simDelay);\r\n }\r\n else {\r\n (options.simDelay === 0) ? sendApi() : setTimeout(function () { sendApi(); }, options.simDelay);\r\n }\r\n\r\n function sendApi()\r\n {\r\n let afterFix = getAfterfix(apiName),\r\n apiUrl = _apiPath + apiName + afterFix + _apiExtension;\r\n\r\n let sendingObj: any =\r\n {\r\n url: apiUrl,\r\n crossDomain: true,\r\n type: method,\r\n async: async,\r\n dataType: _dataType,\r\n\r\n // headers: {\"Authorization\": _token},\r\n\r\n data: JSON.stringify(params),\r\n contentType: 'application/json'\r\n };\r\n\r\n $.ajax\r\n (sendingObj)\r\n .done(complete)\r\n .fail((event) =>\r\n {\r\n if (!async) return;\r\n\r\n console.log(\"API: [\" + apiName + \"] 無法取得伺服器回應\");\r\n console.log(event);\r\n\r\n if (completeWithError) {\r\n reject({ message: \"API: [\" + apiName + \"] 無法取得伺服器回應\" });\r\n }\r\n });\r\n\r\n }\r\n\r\n function complete(response: any)\r\n {\r\n //if(!_cachedData[apiName]) _cachedData[apiName] = response;\r\n\r\n // console.log(response); \r\n\r\n if (options.schema) {\r\n let responseSchema = options.schema.properties.response;\r\n responseSchema[\"$id\"] = \"/\" + apiName + \".response\";\r\n\r\n if (options.schema.definitions) responseSchema.definitions = options.schema.definitions;\r\n\r\n let validatedResult = AjvProxy.validate(responseSchema, params);\r\n if (validatedResult.error) {\r\n reject(validatedResult.error);\r\n return;\r\n }\r\n }\r\n\r\n\r\n if (response.error) {\r\n\r\n if (typeof response.error === \"string\") response.error = { message: response.error };\r\n\r\n console.log(\"api [\" + apiName + \"] error: \" + response.error.message);\r\n\r\n if (completeWithError) {\r\n reject(response.error);\r\n }\r\n }\r\n else {\r\n resolve(response);\r\n }\r\n }\r\n });\r\n },\r\n\r\n applyTestData: function (testData: any)\r\n {\r\n _testData = testData;\r\n },\r\n\r\n getApiPath: function ()\r\n {\r\n return _apiPath;\r\n },\r\n\r\n setApiPath: function (path: string)\r\n {\r\n _apiPath = path;\r\n },\r\n\r\n setApiExtension: function (ext: string)\r\n {\r\n _apiExtension = ext;\r\n },\r\n\r\n setMethod: function (method: string)\r\n {\r\n _method = method;\r\n },\r\n\r\n getMethod: function ()\r\n {\r\n return _method;\r\n },\r\n\r\n setToken(token: string)\r\n {\r\n _token = \"Bearer \" + token;\r\n },\r\n\r\n setLogSendingData: function (b: boolean)\r\n {\r\n _logSendingData = b;\r\n },\r\n\r\n setSimplateDelay: (delay: number) =>\r\n {\r\n _simulateDelay = delay;\r\n },\r\n\r\n /**\r\n * 改變測試 api 回應的資料\r\n *\r\n * @param {string} apiName\r\n * @param {*} params\r\n */\r\n resetTestData: (apiName: string, params: any) =>\r\n {\r\n if (_testData && _testData[apiName] && _testData[apiName].reset) {\r\n _testData[apiName].reset(params);\r\n }\r\n },\r\n\r\n /**\r\n * 依據 sendData 改變測試 api 回應的資料\r\n *\r\n * @param {string} apiName\r\n * @param {*} sendData\r\n */\r\n applyTestSendData: (apiName: string, sendData: any) =>\r\n {\r\n if (_testData && _testData[apiName] && _testData[apiName].applySendData) {\r\n _testData[apiName].applySendData(sendData);\r\n }\r\n },\r\n\r\n setApiResponseAfterfix(apiName: string, afterFix: string)\r\n {\r\n _apiResponseAfterfixs[apiName] = \".\" + afterFix;\r\n }\r\n};\r\n\r\nfunction getAfterfix(apiName: string)\r\n{\r\n let s = _apiResponseAfterfixs[apiName] ? _apiResponseAfterfixs[apiName] : \".response\";\r\n return GS.setting.useApiSchemaData ? s : \"\";\r\n}\r\n\r\nexport default ApiProxy;","import ViewportRoot, { getViewportName, ViewportIndex } from \"./sframe/ViewportRoot\";\r\nimport ApiProxy from \"./ApiProxy\";\r\n\r\nenum StyleModes\r\n{\r\n mobie = 0,\r\n pc = 1,\r\n default = 1\r\n}\r\n\r\nexport enum Direction\r\n{\r\n TopLeft = 7,\r\n Top = 8,\r\n TopRight = 9,\r\n Left = 4,\r\n Center = 5,\r\n Right = 6,\r\n BottomLeft = 1,\r\n Bottom = 2,\r\n BottomRight = 3\r\n}\r\n\r\nconst _localSetting =\r\n{\r\n testEnv: \"local\",\r\n useApiSchemaData: true,\r\n logSendingData: true,\r\n apiPath: \"./static/api-data/\",\r\n apiExt: \".json\",\r\n};\r\n\r\nconst _testSetting =\r\n{\r\n testEnv: \"hikaru-test\",\r\n useApiSchemaData: true,\r\n logSendingData: true,\r\n apiPath: \"./static/api-data/\",\r\n apiExt: \".json\"\r\n};\r\n\r\n\r\nexport default class GS\r\n{\r\n static setting = {\r\n testEnv: \"\",\r\n useApiSchemaData: false,\r\n logSendingData: false,\r\n apiPath: \"/api/frontend/\",\r\n apiExt: \"\"\r\n };\r\n\r\n static reviewMaxRating: number = 5;\r\n\r\n static search = \r\n {\r\n keywordMaxLength: 20,\r\n pageSize: 10,\r\n getQueryUrl: (queryString: string)=>\r\n {\r\n return ApiProxy.getCommonPageData().search_page_path + \"?\" + queryString\r\n }\r\n }\r\n\r\n static ezpointRatio = \r\n {\r\n pointToCash: .1,\r\n priceToPoint: .02\r\n };\r\n\r\n static ezpointToCash(value: number)\r\n {\r\n return Math.floor(GS.ezpointRatio.pointToCash * value);\r\n }\r\n\r\n static priceToEzpoint(value: number)\r\n {\r\n return Math.round(GS.ezpointRatio.priceToPoint * value);\r\n }\r\n\r\n static SPORTS_VOUCHER_BONUS_RATE: number = .2;\r\n\r\n static getSportsVoucherBonus(amount: number)\r\n {\r\n return Math.ceil(amount * this.SPORTS_VOUCHER_BONUS_RATE);\r\n }\r\n\r\n static DATE_FORMAT: string = \"YYYY-MM-DD\";\r\n\r\n static DEFAULT_COUNTRY: string = \"tw\";\r\n\r\n static navHeight = {\r\n 0: 80,\r\n 1: 80\r\n };\r\n\r\n static init()\r\n {\r\n if (window.location.host === \"local.savorks.com\") {\r\n $.extend(this.setting, _localSetting);\r\n }\r\n else if (window.location.host === \"www.hikaru.com.tw\") {\r\n $.extend(this.setting, _testSetting);\r\n }\r\n\r\n ApiProxy.setLogSendingData(this.setting.logSendingData);\r\n }\r\n\r\n static getNavHeight()\r\n {\r\n let i: StyleModes = ViewportRoot.index,\r\n v = this.navHeight[i];\r\n return v === undefined ? this.navHeight[StyleModes.default] : v;\r\n }\r\n}","import ComponentBase from \"@root/components/ComponentBase\";\r\nimport Popups from \"../components/popups/Popups\";\r\nimport GS from \"./GlobalSetting\";\r\nimport { gsap } from \"gsap\";\r\nimport { ComponentElem } from \"@root/components/interfaces\";\r\nimport { Common } from \"@root/components/common/CommonTypes\";\r\nimport { getViewportName } from \"./sframe/ViewportRoot\";\r\nimport EZAlert from \"../components/popups/EZAlert\";\r\n\r\nlet _customStyleElem: HTMLElement;\r\n\r\nlet Tools =\r\n{\r\n init: () =>\r\n {\r\n window.EZ.Tools = Tools;\r\n\r\n if (!_customStyleElem) _customStyleElem = document.createElement('style');\r\n _customStyleElem.id = 'custom-page-style';\r\n $('head').append(_customStyleElem);\r\n },\r\n\r\n /**\r\n * 檢查是字串內容是否可以轉化為數字\r\n *\r\n * @param {string} str\r\n * @return {*} {boolean}\r\n */\r\n isNumeric: (str: string): boolean => \r\n {\r\n if (typeof str != \"string\") return false;\r\n ///@ts-ignore\r\n return !isNaN(str) && !isNaN(parseFloat(str));\r\n },\r\n\r\n stringReplaceBetween(s: string, start: number, length: number, replaceString: string)\r\n {\r\n return s.substring(0, start) + replaceString + s.substring(start + length);\r\n },\r\n\r\n combineName(firstName: string, surname: string): string\r\n {\r\n if (firstName) {\r\n if (firstName.match(/[\\u4e00-\\u9fa5]/g)) {\r\n return surname ?\r\n surname + firstName :\r\n firstName;\r\n }\r\n else {\r\n return surname ?\r\n firstName + \" \" + surname :\r\n firstName;\r\n }\r\n }\r\n else {\r\n return null;\r\n }\r\n },\r\n\r\n createLazyLoadImage(src: string)\r\n {\r\n let img = new Image();\r\n img.setAttribute(\"lazy-loading\", \"manual\");\r\n img.onload = () =>\r\n {\r\n img.setAttribute(\"lazy-loading\", \"done\");\r\n };\r\n img.src = src;\r\n return img;\r\n },\r\n\r\n formatDateString(dateString: string, format: string = 'YYYY/MM/DD')\r\n {\r\n ///@ts-ignore\r\n let date = moment(dateString);\r\n return date.format(format);\r\n },\r\n\r\n getAvatarName(name: string)\r\n {\r\n name = name.replace(/ +/g, ' ');\r\n let length = 2;\r\n\r\n // let r = name.match(/\\p{Script=Han}+|\\p{Script=Latin}+/gu);\r\n // 符合中文的文字\r\n let r = name.match(/[\\u4e00-\\u9fa5]/g);\r\n console.log(r);\r\n\r\n if (r) {\r\n if (r.length === 2 || r.length === 3) {\r\n name = r[r.length - 2] + r[r.length - 1];\r\n }\r\n else {\r\n name = r[0];\r\n length = 1;\r\n }\r\n }\r\n else {\r\n // let array = name.match(/\\p{Script=Han}+|\\p{Script=Latin}+/gu);\r\n // console.log(array);\r\n\r\n name = name[0];\r\n }\r\n\r\n return name;\r\n\r\n // return `https://ui-avatars.com/api/?length=${length}&${name}`;\r\n },\r\n\r\n // generateAvatar(text: any, foregroundColor: any = \"#ffffff\", backgroundColor: any = \"#000000\")\r\n // {\r\n // const canvas = document.createElement(\"canvas\");\r\n // const context = canvas.getContext(\"2d\");\r\n\r\n // canvas.width = 200;\r\n // canvas.height = 200;\r\n\r\n // // Draw background\r\n // context.fillStyle = backgroundColor;\r\n // context.fillRect(0, 0, canvas.width, canvas.height);\r\n\r\n // // Draw text\r\n // context.font = \"bold 100px Assistant\";\r\n // context.fillStyle = foregroundColor;\r\n // context.textAlign = \"center\";\r\n // context.textBaseline = \"middle\";\r\n // context.fillText(text, canvas.width / 2, canvas.height / 2);\r\n\r\n // return canvas.toDataURL(\"image/png\");\r\n // },\r\n\r\n completeLinks($container: any)\r\n {\r\n let $elems = $container.find('[popup-to]');\r\n $.each($elems, (index: number, elem: HTMLElement) =>\r\n {\r\n $(elem).on(\"click\", (event: Event) =>\r\n {\r\n event.preventDefault();\r\n Popups.to(elem.getAttribute(\"popup-to\"));\r\n });\r\n });\r\n\r\n $elems = $container.find(`[deeplink]`);\r\n $.each($elems, (index: number, elem: HTMLElement) =>\r\n {\r\n $(elem).on(\"click\", (event: Event) =>\r\n {\r\n event.preventDefault();\r\n\r\n let target = elem.getAttribute(\"deeplink\"),\r\n offsetY = GS.getNavHeight();\r\n gsap.to(window, { duration: .5, scrollTo: { y: target, offsetY: offsetY } });\r\n });\r\n });\r\n },\r\n\r\n\r\n\r\n seekComponentByName(componentName: string, $elem?: JQuery): any\r\n {\r\n return Tools.seekComponent(`[component=\"${componentName}\"]`, $elem);\r\n },\r\n\r\n seekComponentById(id: string, $elem?: JQuery): any\r\n {\r\n return Tools.seekComponent(`#${id}`, $elem);\r\n },\r\n\r\n seekComponentByClass(className: string, $elem?: JQuery): any\r\n {\r\n return Tools.seekComponent(`.${className}`, $elem);\r\n },\r\n\r\n seekComponentElem($elem: JQuery, componentName: string): ComponentElem\r\n {\r\n return ($elem.find(`[component=\"${componentName}\"]`)[0]);\r\n },\r\n\r\n seekComponent(query: string, $elem?: JQuery): any\r\n {\r\n if (!$elem) $elem = $(`body`);\r\n return ($elem.find(query)[0])._component;\r\n },\r\n\r\n\r\n removeBetweenArrays(targetList: any[], sourceList: any[])\r\n {\r\n targetList.forEach((target: any) =>\r\n {\r\n sourceList.forEach((source, index, obj) =>\r\n {\r\n if (target === source) {\r\n obj.splice(index, 1);\r\n }\r\n });\r\n });\r\n },\r\n\r\n formatNumber(num: string | number)\r\n {\r\n if (typeof num === \"string\") {\r\n return parseInt(num).toLocaleString(\"zh-tw\");\r\n }\r\n else {\r\n return (num).toLocaleString(\"zh-tw\");\r\n }\r\n\r\n },\r\n\r\n testRectOverlap(rectA: DOMRect, rectB: DOMRect)\r\n {\r\n return (rectA.left < rectB.right && rectA.right > rectB.left &&\r\n rectA.top < rectB.bottom && rectA.bottom > rectB.top);\r\n },\r\n\r\n testRectOverlapY(rectA: DOMRect, rectB: DOMRect)\r\n {\r\n return (\r\n rectA.top < rectB.bottom && rectA.bottom > rectB.top);\r\n },\r\n\r\n addCustomStyle(query: string, styles: string, isMobile: boolean = false)\r\n {\r\n if (isMobile) _customStyleElem.innerHTML = _customStyleElem.innerHTML + \"@media(max-width: 640px){\" + query + \"{\" + styles + \"}}\" + '\\n';\r\n else _customStyleElem.innerHTML = _customStyleElem.innerHTML + query + \"{\" + styles + \"}\" + '\\n';\r\n },\r\n\r\n // 功能完整版\r\n addCustomStyle_2(id: string, query: string, styles: string, isMobile: boolean = false, isReplace: boolean = true)\r\n {\r\n let elem = $(`#${id}`)[0];\r\n\r\n if (!elem) {\r\n elem = document.createElement('style');\r\n elem.id = id;\r\n $('head').append(elem);\r\n }\r\n\r\n let newHtml = isMobile ? \"@media(max-width: 640px){\" + query + \"{\" + styles + \"}}\" + '\\n' : query + \"{\" + styles + \"}\" + '\\n';\r\n elem.innerHTML = isReplace ? newHtml : elem.innerHTML + newHtml;\r\n },\r\n\r\n\r\n /**\r\n * 捲動到目標 HTMLElement\r\n *\r\n * @param {HTMLElement} elem\r\n * @param {{mobile:number, pc:number}} [offset={mobile: 20, pc: 20}]\r\n */\r\n scrollToElement(elemOrId: HTMLElement | string, offset: { mobile: number, pc: number } | number = { mobile: 0, pc: 0 }): void\r\n {\r\n\r\n let navHeight = GS.getNavHeight(),\r\n ///@ts-ignore\r\n basiceOffset = typeof offset === \"number\" ? offset : ((getViewportName() === \"mobile\" ? offset.mobile : offset.pc)),\r\n offsetY = navHeight + basiceOffset;\r\n\r\n if (typeof elemOrId === \"string\") elemOrId = \"#\" + elemOrId;\r\n\r\n\r\n gsap.to(window, { duration: .5, scrollTo: { y: elemOrId, offsetY: offsetY } });\r\n },\r\n\r\n listToDic(list: any[], key?: string)\r\n {\r\n let dic: any = {},\r\n obj: any;\r\n\r\n if (key) {\r\n for (let i = 0; i < list.length; i++) {\r\n obj = list[i];\r\n dic[obj[key]] = obj;\r\n }\r\n }\r\n else {\r\n for (let i = 0; i < list.length; i++) {\r\n obj = list[i];\r\n dic[obj] = obj;\r\n }\r\n }\r\n\r\n return dic;\r\n },\r\n\r\n\r\n\r\n splitText: function (dom: any)\r\n {\r\n // gsap.set(dom, {perspective: 400});\r\n\r\n // console.log(dom);\r\n\r\n var string = dom.innerText;\r\n dom.innerHTML = \"\";\r\n\r\n //console.log(dom);\r\n //console.log(string);\r\n\r\n var i,\r\n char: string,\r\n charDom,\r\n array = [];\r\n\r\n for (i = 0; i < string.length; i++) {\r\n charDom = document.createElement(\"span\");\r\n char = string.slice(i, i + 1);\r\n\r\n // console.log(`${char}: ${char.charCodeAt(0)}`);\r\n\r\n if (char === \" \") {\r\n charDom.innerHTML = \" \";\r\n }\r\n else if(char.charCodeAt(0) === 10)\r\n { \r\n // charDom.innerHTML = `
`;\r\n charDom = document.createElement(\"br\");\r\n }\r\n else {\r\n charDom.innerHTML = char;\r\n }\r\n\r\n $(charDom).css\r\n ({\r\n \"display\": \"inline-block\",\r\n \"position\": \"relative\"\r\n });\r\n\r\n dom.appendChild(charDom);\r\n\r\n array.push(charDom);\r\n }\r\n\r\n return array;\r\n },\r\n\r\n /**\r\n * 轉換 json 內容為布林值\r\n *\r\n * @param {*} value\r\n * @return {*} {boolean}\r\n */\r\n parseBoolean(value: any): boolean\r\n {\r\n if (value === true) return true;\r\n if (value === \"true\") return true;\r\n return false;\r\n },\r\n\r\n parseInt(value: any): number\r\n {\r\n return parseInt(value);\r\n },\r\n\r\n parseFloat(value: any): number\r\n {\r\n return parseFloat(value);\r\n },\r\n\r\n parseDate(value: string | moment.Moment, addSpace: boolean = true): string\r\n {\r\n /// @ts-ignore\r\n let date: moment.Moment = typeof value === \"string\" ? moment(value, GS.DATE_FORMAT) : value;\r\n let result = addSpace ? date.format(\"YYYY / MM / DD\") : date.format(\"YYYY/MM/DD\");\r\n return result;\r\n },\r\n\r\n parseValue(value: string, valueType: string)\r\n {\r\n let v: any = value;\r\n if (valueType === \"int\") v = Tools.parseInt(value);\r\n else if (valueType === \"float\") v = Tools.parseFloat(value);\r\n else if (valueType === \"boolean\") v = Tools.parseBoolean(value);\r\n else if (valueType === \"gender\") v = value === \"male\" ? \"男\" : \"女\";\r\n else if (valueType === \"date\") v = Tools.parseDate(value);\r\n else if (valueType === \"date2\") v = Tools.parseDate(value, false);\r\n\r\n return v;\r\n },\r\n\r\n updateValue($target: JQuery, valueName: string, value: string | number, valueType: string = \"string\")\r\n {\r\n let v: any = Tools.parseValue(String(value), valueType);\r\n\r\n $target.find(`[data-value=\"${valueName}\"]`).text(v);\r\n },\r\n\r\n getFullName(firstName: string, surname: string)\r\n {\r\n let reg = /[^\\u4e00-\\u9fa5]/,\r\n isNotChinese = reg.test(firstName);\r\n\r\n return isNotChinese ?\r\n firstName + \" \" + surname :\r\n surname + \"\" + firstName;\r\n },\r\n\r\n /**\r\n * 取出 localStorage 資料 (資料以 json字串 儲存, 取出後將轉換為 json物件)\r\n * @param name 物件名稱\r\n * @returns \r\n */\r\n getLocalStorageObj(name: string)\r\n {\r\n let objString: string = localStorage.getItem(name),\r\n obj: any;\r\n\r\n try {\r\n obj = JSON.parse(objString);\r\n }\r\n catch (e) { }\r\n\r\n if (!obj || typeof obj !== \"object\") obj = {};\r\n\r\n return obj;\r\n },\r\n\r\n /**\r\n * 儲存 localStorage 資料 (資料將被轉換成 json字串 後儲存)\r\n * @param name 物件名稱\r\n * @param obj 資料\r\n */\r\n saveLocalStorageObj(name: string, obj: any)\r\n {\r\n localStorage.setItem(name, JSON.stringify(obj));\r\n },\r\n\r\n isLocalStorageAvailable()\r\n {\r\n var test = 'test';\r\n try {\r\n localStorage.setItem(test, test);\r\n localStorage.removeItem(test);\r\n return true;\r\n } catch (e) {\r\n EZAlert.alert(\"提示\", `本網站需要使用瀏覽器的localStorage,未啟用將無法使用部分功能`);\r\n return false;\r\n }\r\n },\r\n\r\n shake(elem: HTMLElement)\r\n {\r\n let duration = .1,\r\n ease = \"quad.inOut\"\r\n\r\n gsap.to(elem, {\r\n duration: duration,\r\n x: -7,\r\n ease: ease\r\n });\r\n gsap.to(elem, {\r\n duration: duration,\r\n repeat: 4,\r\n x: 7,\r\n yoyo: true,\r\n delay: .1,\r\n ease: ease,\r\n clearProps: \"transform\"\r\n });\r\n // gsap.to(elem, {\r\n // duration: duration,\r\n // x: 0,\r\n // delay: .1 * 4\r\n // });\r\n },\r\n\r\n // 限制 input 輸入字元\r\n setInputFilter(textbox: Element, inputFilter: (value: string) => boolean, errMsg: string): void\r\n {\r\n [\"input\", \"keydown\", \"keyup\", \"mousedown\", \"mouseup\", \"select\", \"contextmenu\", \"drop\", \"focusout\"].forEach(function (event)\r\n {\r\n textbox.addEventListener(event, function (this: (HTMLInputElement | HTMLTextAreaElement) & { oldValue: string; oldSelectionStart: number | null, oldSelectionEnd: number | null })\r\n {\r\n if (inputFilter(this.value)) {\r\n this.oldValue = this.value;\r\n this.oldSelectionStart = this.selectionStart;\r\n this.oldSelectionEnd = this.selectionEnd;\r\n }\r\n else if (Object.prototype.hasOwnProperty.call(this, \"oldValue\")) {\r\n this.value = this.oldValue;\r\n\r\n if (this.oldSelectionStart !== null &&\r\n this.oldSelectionEnd !== null) {\r\n this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);\r\n }\r\n }\r\n else {\r\n this.value = \"\";\r\n }\r\n });\r\n });\r\n }\r\n};\r\n\r\nexport default Tools;","let Geom = {\r\n caculateContain: function (containerWidth: number, containerHeight: number, contentWidth: number, contentHeight: number)\r\n {\r\n var containerRatio = containerWidth / containerHeight,\r\n contentRatio = contentWidth / contentHeight,\r\n width, height;\r\n\r\n if (contentRatio > containerRatio) {\r\n width = containerWidth;\r\n height = width / contentRatio;\r\n }\r\n else {\r\n height = containerHeight;\r\n width = height * contentRatio;\r\n }\r\n\r\n return { width: width, height: height, ratio: width / contentWidth };\r\n },\r\n\r\n caculateCover: function (containerWidth: number, containerHeight: number, contentWidth: number, contentHeight: number)\r\n {\r\n var containerRatio = containerWidth / containerHeight,\r\n contentRatio = contentWidth / contentHeight,\r\n width, height;\r\n\r\n if (contentRatio > containerRatio) {\r\n height = containerHeight;\r\n width = height * contentRatio;\r\n }\r\n else {\r\n width = containerWidth;\r\n height = width / contentRatio;\r\n }\r\n\r\n var ratio = width / contentWidth;\r\n\r\n var tw = containerWidth / ratio,\r\n th = containerHeight / ratio;\r\n var contentCenterBound =\r\n {\r\n x: (contentWidth - tw) * .5,\r\n y: (contentHeight - th) * .5,\r\n width: tw,\r\n height: th\r\n };\r\n\r\n //console.log(contentCenterBound);\r\n\r\n return { width: width, height: height, ratio: ratio, contentCenterBound: contentCenterBound };\r\n }\r\n};\r\n\r\nexport default Geom;","/* change log\r\n 1.0.1\r\n 支援 addListener 和 removeListener\r\n 移除 bind, 統一由 listener 回應捲動事件\r\n 1.0.2\r\n 新增 update 方法, 強制更新狀態\r\n 改變 testDom 方法的判定\r\n 新增 testDomFromMiddle 方法, 類似 testDom, 但是判定內容以判定目標的中間為準\r\n 1.0.3\r\n 新增 getPosition 方法, 取用目前 window scrollTop 和 scrollLeft\r\n 1.0.4\r\n testDomFromMiddle 新增 containerSizeScale 參數, 決定判斷容器的大小\r\n 1.0.5\r\n scroll height 取得方式變更\r\n 1.0.7\r\n 更換成 typescript, 移置 sframe namespace\r\n */\r\n\r\nimport gsap from \"gsap\";\r\nimport Utility from \"./Utility\";\r\n\r\n\r\nvar _isLocking = false,\r\n _listenerDic: { [key: string]: Function } = {},\r\n _scrollBound =\r\n {\r\n top: 0,\r\n left: 0,\r\n width: 0,\r\n height: 0\r\n },\r\n _tweenDic = { scrollTop: 0, scrollLeft: 0 };\r\n\r\nvar self =\r\n{\r\n _isActive: false,\r\n\r\n init: function ()\r\n {\r\n window.scrollTo(0, 0);\r\n\r\n return self;\r\n },\r\n\r\n addListener: function (id: string, func: Function)\r\n {\r\n _listenerDic[id] = func;\r\n\r\n return self;\r\n },\r\n\r\n removeListener: function (id: string)\r\n {\r\n delete _listenerDic[id];\r\n\r\n return self;\r\n },\r\n\r\n active: function ()\r\n {\r\n self._isActive = true;\r\n\r\n window.addEventListener(\"scroll\", onScroll);\r\n\r\n updateScrollTop();\r\n\r\n return self;\r\n },\r\n\r\n disactive: function ()\r\n {\r\n self._isActive = false;\r\n window.removeEventListener(\"scroll\", onScroll);\r\n\r\n return self;\r\n },\r\n\r\n // topOffset and bottomOffset 代表檢測內容的上下偏移 (1.0.1版為容器的上下偏移)\r\n testDom: function (dom: HTMLElement, topOffset: number, bottomOffset: number)\r\n {\r\n var bound = dom.getBoundingClientRect();\r\n\r\n if (topOffset === undefined) topOffset = 0;\r\n if (bottomOffset === undefined) bottomOffset = 0;\r\n\r\n var top = 0,\r\n bottom = _scrollBound.height;\r\n\r\n var boundTop = bound.top + topOffset,\r\n boundBottom = bound.bottom + bottomOffset;\r\n\r\n var topInside = (boundTop >= top && boundTop <= bottom),\r\n bottomInside = (boundBottom >= top && boundBottom <= bottom);\r\n\r\n return {\r\n bound: bound,\r\n topInside: topInside,\r\n bottomInside: bottomInside\r\n };\r\n },\r\n\r\n // 測試目標 boundingClientRect 中線是否進入 viewport 中\r\n testDomFromMiddle: function (dom: HTMLElement, topOffset: number = 0, bottomOffset: number = 0)\r\n {\r\n var bound = dom.getBoundingClientRect();\r\n\r\n if (topOffset === undefined) topOffset = 0;\r\n if (bottomOffset === undefined) bottomOffset = 0;\r\n\r\n var top = 0,\r\n bottom = _scrollBound.height;\r\n\r\n var boundMiddle = bound.top + bound.height * .5,\r\n boundTop = boundMiddle + topOffset,\r\n boundBottom = boundMiddle + bottomOffset;\r\n\r\n // console.log(bound);\r\n // console.log(top);\r\n // console.log(bottom);\r\n // console.log(boundMiddle);\r\n\r\n var topInside = (boundTop >= top && boundTop <= bottom),\r\n bottomInside = (boundBottom >= top && boundBottom <= bottom);\r\n\r\n return {\r\n bound: bound,\r\n topInside: topInside,\r\n bottomInside: bottomInside,\r\n bothInside: topInside && bottomInside,\r\n test: _scrollBound.height\r\n };\r\n },\r\n\r\n // 測試目標 boundingClientRect 是否包含 viewport 中線\r\n testDomContainScreenMiddle: function (dom: HTMLElement)\r\n {\r\n var bound = dom.getBoundingClientRect(),\r\n screenMiddleY = _scrollBound.height * .5;\r\n\r\n return (bound.top <= screenMiddleY) && (bound.top + bound.height >= screenMiddleY);\r\n },\r\n\r\n scrollTo: function (targetTop: number, cb: Function, __speed: number)\r\n {\r\n\r\n var speed = __speed ? __speed : 1000,\r\n //dy = Math.abs(targetTop - _tweenDic.scrollTop),\r\n dy = Math.abs(targetTop - $(window).scrollTop()),\r\n duration = Math.min(.8, dy / speed);\r\n\r\n gsap.killTweensOf(_tweenDic);\r\n _tweenDic.scrollTop = $(window).scrollTop();\r\n\r\n gsap.to(_tweenDic, {\r\n duration: duration, scrollTop: targetTop, onStart: function ()\r\n {\r\n }, onUpdate: function ()\r\n {\r\n //console.log(_tweenDic.scrollTop + \" : \" + $(window).scrollTop());\r\n window.scrollTo($(window).scrollLeft(), _tweenDic.scrollTop);\r\n //$(window).scrollTop(_tweenDic.scrollTop);\r\n\r\n }, onComplete: cb as gsap.Callback\r\n });\r\n },\r\n\r\n update: function (specId?: string)\r\n {\r\n updateScrollTop(true, specId);\r\n },\r\n\r\n getPosition: function ()\r\n {\r\n return _tweenDic;\r\n }\r\n};\r\n\r\nfunction onScroll(evt: Event){\r\n updateScrollTop();\r\n}\r\n\r\nfunction updateScrollTop(forceExecute?: boolean, specId?: string)\r\n{\r\n if (!forceExecute && _isLocking) return;\r\n if (!forceExecute && !self._isActive) return;\r\n\r\n var doc = document.documentElement;\r\n _scrollBound.left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);\r\n _scrollBound.top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);\r\n _scrollBound.width = $(window).width();\r\n\r\n // _scrollBound.height = $(window).height();\r\n _scrollBound.height = window.innerHeight;\r\n\r\n if (Utility.getMobileOperatingSystem() == \"iOS\") {\r\n _scrollBound.height = document.documentElement.clientHeight;\r\n }\r\n\r\n // console.log(_scrollBound.top);\r\n // console.log(\"t: \" + (window.pageYOffset || document.documentElement.scrollTop));\r\n\r\n _tweenDic.scrollTop = $(window).scrollTop();\r\n _tweenDic.scrollLeft = $(window).scrollLeft();\r\n\r\n //if(_cbOnScrolling) _cbOnScrolling.call(null, _scrollBound);\r\n var id, func;\r\n for (id in _listenerDic) {\r\n func = _listenerDic[id];\r\n if (specId) {\r\n if (specId === id) {\r\n func.call(null, _scrollBound);\r\n }\r\n }\r\n else {\r\n func.call(null, _scrollBound);\r\n }\r\n }\r\n};\r\n\r\nexport var ScrollListener = self;\r\n\r\n\r\n","/** Utility\r\n *\r\n * 2019/9/3 v0.0.17\r\n * 新增 Utility.realStringLength 方法\r\n * 新增 Utility.getMobileOperatingSystem 方法\r\n * 新增 PatternSamples[\"invoice\"] 發票號碼正規式\r\n *\r\n * 2019/12/12 v0.0.18\r\n * 新增 Utility.openWindowInCenter 方法\r\n * 新增 Utility.isWebview 方法, 判別是否是 webview 瀏覽器 (line / facebook app)\r\n *\r\n * 2020/07/05\r\n * 新增 detectIE 方法, 判別IE版本\r\n * \r\n * 2020/12/21\r\n * 語法整理\r\n * 新增 isMobile 方法, 判別是否為 mobile 平台\r\n * 抽離 PatternSamples 合集到另一個檔案\r\n * analysisUrl 方法改名為 isUrl\r\n * \r\n * 2021/03/25\r\n * 改寫成 typescript, 移至 sframe namespace\r\n * \r\n * 2021/11/02\r\n * 新增 copyTextToClipboard 方法, 複製文字到剪貼簿\r\n * \r\n * \r\n * 2023/4/5\r\n * 新增 adjustUrlParams 方法, 調整 url query\r\n * \r\n * **/\r\n\r\nvar _mobileSystem: string;\r\n\r\nlet self;\r\n\r\nlet Utility = self =\r\n{\r\n detectIE: function (): boolean | number\r\n {\r\n var ua = window.navigator.userAgent;\r\n\r\n var msie = ua.indexOf('MSIE ');\r\n if (msie > 0) {\r\n // IE 10 or older => return version number\r\n return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);\r\n }\r\n\r\n var trident = ua.indexOf('Trident/');\r\n if (trident > 0) {\r\n // IE 11 => return version number\r\n var rv = ua.indexOf('rv:');\r\n return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);\r\n }\r\n\r\n var edge = ua.indexOf('Edge/');\r\n if (edge > 0) {\r\n // Edge (IE 12+) => return version number\r\n return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);\r\n }\r\n\r\n // other browser\r\n return false;\r\n },\r\n\r\n /**\r\n * 檢查字串是否為網址\r\n * @param url 網址\r\n * @returns\r\n */\r\n isUrl: function (url: string): boolean\r\n {\r\n var pattern = /^((http[s]?|ftp):\\/)?\\/?([^:\\/\\s]+)((\\/\\w+)*\\/)([\\w\\-\\.]+[^#?\\s]+)(.*)?(#[\\w\\-]+)?$/;\r\n return Boolean(url.match(pattern));\r\n },\r\n\r\n /**\r\n * 取得當前當前網址路徑含檔名.副檔名\r\n * @returns \r\n */\r\n getPathWithFilename: function (): string\r\n {\r\n return window.location.href.replace(window.location.search, '').replace(window.location.hash, '').replace(\"?\", '');\r\n },\r\n\r\n /**\r\n * 取得網址路徑(不包含檔名)\r\n * @param url 網址\r\n * @returns \r\n */\r\n getPath: function (url?: string): string\r\n {\r\n if (!url) url = window.location.href;\r\n\r\n var string = url.indexOf('?') == -1 ? url : url.substr(0, url.indexOf('?'));\r\n\r\n if (string.indexOf('#') !== -1) {\r\n string = string.substr(0, string.indexOf('#'));\r\n }\r\n\r\n var array = string.split(\"/\");\r\n var lastPart = array[array.length - 1];\r\n if (lastPart.indexOf(\".\") !== -1) array.pop();\r\n\r\n string = array.join(\"/\");\r\n\r\n\r\n while (string.length && string.charAt(string.length - 1) == \"/\") {\r\n string = string.slice(0, string.length - 1);\r\n }\r\n\r\n string += \"/\";\r\n\r\n return string;\r\n },\r\n\r\n /**\r\n * 取得網址通訊協定(https, https ...)\r\n * @param url 網址\r\n * @returns \r\n */\r\n getProtocol: function (url: string): string\r\n {\r\n if (!url) url = window.location.href;\r\n return url.split(\"/\")[0];\r\n },\r\n\r\n /**\r\n * 網址參數 (此方法將快取一開始的網址, 如欲取得目前的網址參數, 請改用 getCurrentUrlParams)\r\n */\r\n urlParams: (function ()\r\n {\r\n return getUrlParams();\r\n\r\n }()) as { [key: string]: string },\r\n\r\n getCurrentUrlParams()\r\n {\r\n return getUrlParams();\r\n },\r\n\r\n replaceUrlParams(newQuery?: string, keepState: boolean = true)\r\n {\r\n let currentUrl = window.location.href.split(`?`)[0];\r\n // console.log(currentUrl);\r\n\r\n var newUrl = newQuery ? currentUrl + \"?\" + newQuery : currentUrl;\r\n keepState? window.history.replaceState({}, document.title, newUrl): window.history.pushState({}, document.title, newUrl);\r\n },\r\n\r\n adjustUrlParams(removingList?: string[], addingDic?: {[key:string]:string})\r\n {\r\n if (!window.history) return;\r\n\r\n let params = Utility.getCurrentUrlParams(),\r\n string = \"\",\r\n key,\r\n k,\r\n i = 0;\r\n\r\n for (key in params) {\r\n let flag = false;\r\n\r\n if (removingList) {\r\n for (k = 0; k < removingList.length; k++) {\r\n let removingKey: string = removingList[k];\r\n if (key === removingKey) flag = true;\r\n }\r\n }\r\n\r\n\r\n if (addingDic && addingDic[key]) flag = true;\r\n\r\n if (flag) continue;\r\n\r\n if (i === 0) {\r\n string += (\"?\" + key + \"=\" + params[key]);\r\n }\r\n else {\r\n string += (\"&\" + key + \"=\" + params[key]);\r\n }\r\n\r\n i++;\r\n }\r\n\r\n\r\n if (addingDic) {\r\n for (key in addingDic) {\r\n if (string === \"\") {\r\n string += (\"?\" + key + \"=\" + addingDic[key]);\r\n }\r\n else {\r\n string += (\"&\" + key + \"=\" + addingDic[key]);\r\n }\r\n }\r\n }\r\n\r\n var newUrl = Utility.getPathWithFilename() + string;\r\n window.history.replaceState({}, document.title, newUrl);\r\n\r\n return newUrl;\r\n },\r\n\r\n /**\r\n * @returns boolean, 當前網址是否為本機網址\r\n */\r\n isLocalhost: (): boolean =>\r\n {\r\n var address = window.location.toString().split(\"/\")[2];\r\n return (address == \"localhost\" || address.split(\".\")[0] == \"192\");\r\n },\r\n\r\n /**\r\n * \r\n * @returns boolean, 裝置是否為 ios\r\n */\r\n isiOS: function (): boolean\r\n {\r\n return [\r\n 'iPad Simulator',\r\n 'iPhone Simulator',\r\n 'iPod Simulator',\r\n 'iPad',\r\n 'iPhone',\r\n 'iPod'\r\n ].includes(navigator.platform)\r\n // iPad on iOS 13 detection\r\n || (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document)\r\n },\r\n\r\n /**\r\n * 把字串中的全形字當兩個字元長度計算, 回應字串總長度\r\n * @param string\r\n * @returns \r\n */\r\n getRealStringLength: function (string: string): number\r\n {\r\n return string.replace(/[^\\x00-\\xff]/g, \"**\").length;\r\n },\r\n\r\n /**\r\n * 平台判定 (此語法版本可能過舊)\r\n * @returns \r\n */\r\n getMobileOperatingSystem: function ()\r\n {\r\n if (_mobileSystem) return _mobileSystem;\r\n\r\n var userAgent = navigator.userAgent || navigator.vendor;\r\n\r\n _mobileSystem = \"unknown\"\r\n\r\n if (/windows phone/i.test(userAgent)) {\r\n _mobileSystem = \"Windows Phone\";\r\n }\r\n\r\n if (/android/i.test(userAgent)) {\r\n _mobileSystem = \"Android\";\r\n }\r\n\r\n //@ts-ignore\r\n if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {\r\n\r\n _mobileSystem = \"iOS\";\r\n }\r\n\r\n return _mobileSystem;\r\n },\r\n\r\n /**\r\n * @returns boolean, 裝置是否為 mobile\r\n */\r\n isMobile: function (): boolean\r\n {\r\n return (typeof window.orientation !== \"undefined\") || (navigator.userAgent.indexOf('IEMobile') !== -1);\r\n },\r\n\r\n /**\r\n * @returns boolean, 裝置是否為 app 內建瀏覽器(webview)?\r\n */\r\n isWebview: function (): boolean\r\n {\r\n var u = navigator.userAgent,\r\n ua = navigator.userAgent.toLowerCase(),\r\n weixinMatch = ua.match(/MicroMessenger/i),\r\n isLineApp = u.indexOf(\"Line\") > -1, // Line 內建瀏覽器\r\n isFbApp = u.indexOf(\"FBAV\") > -1, // FB App 內建瀏覽器\r\n isWeixinApp = weixinMatch !== null // 微信內建瀏覽器\r\n\r\n return !!(isLineApp || isFbApp || isWeixinApp);\r\n },\r\n\r\n\r\n /**\r\n * 在螢幕中央打開新視窗\r\n * @param url\r\n * @param title\r\n * @param w 視窗寬度\r\n * @param h 視窗高度\r\n */\r\n openWindowInCenter: function (url: string, title: string, w: number, h: number)\r\n {\r\n // Fixes dual-screen position Most browsers Firefox\r\n var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : window.screenX;\r\n var dualScreenTop = window.screenTop != undefined ? window.screenTop : window.screenY;\r\n\r\n var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;\r\n var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;\r\n\r\n var systemZoom = width / window.screen.availWidth;\r\n var left = (width - w) / 2 / systemZoom + dualScreenLeft;\r\n var top = (height - h) / 2 / systemZoom + dualScreenTop;\r\n //var string = ', width=' + w / systemZoom + ', height=' + h / systemZoom + ', top=' + top + ', left=' + left;\r\n var string = ', width=' + w + ', height=' + h + ', top=' + top + ', left=' + left;\r\n\r\n //var newWindow = window.open(url, title, 'scrollbars=yes' + string);\r\n var newWindow = window.open(url, title, \"toolbar=yes,location=yes,directories=no,status=no,menubar=yes,scrollbars=yes,resizable=yes, copyhistory=yes\" + string);\r\n\r\n // Puts focus on the newWindow \r\n ///@ts-ignore\r\n if (window.focus) newWindow.focus();\r\n },\r\n\r\n copyTextToClipboard(text: string, cb?: (success: boolean) => void)\r\n {\r\n if (!navigator.clipboard) {\r\n fallbackCopyTextToClipboard(text, cb);\r\n return;\r\n }\r\n navigator.clipboard.writeText(text).then(function ()\r\n {\r\n // console.log('Async: Copying to clipboard was successful!');\r\n if (cb) cb.call(null, true);\r\n }, function (err)\r\n {\r\n // console.error('Async: Could not copy text: ', err);\r\n if (cb) cb.call(null, false);\r\n });\r\n }\r\n};\r\n\r\nexport default Utility;\r\n\r\n\r\n\r\nfunction fallbackCopyTextToClipboard(text: string, cb?: (success: boolean) => void)\r\n{\r\n var textArea = document.createElement(\"textarea\");\r\n textArea.value = text;\r\n\r\n // Avoid scrolling to bottom\r\n textArea.style.top = \"-600px\";\r\n textArea.style.left = \"-600px\";\r\n // textArea.style.display = \"none\";\r\n textArea.style.position = \"fixed\";\r\n\r\n\r\n document.body.appendChild(textArea);\r\n textArea.focus();\r\n textArea.select();\r\n\r\n try {\r\n var successful = document.execCommand('copy');\r\n // var msg = successful ? 'successful' : 'unsuccessful';\r\n // console.log('Fallback: Copying text command was ' + msg);\r\n if (cb) cb.call(null, successful);\r\n\r\n } catch (err) {\r\n if (cb) cb.call(null, false);\r\n }\r\n\r\n document.body.removeChild(textArea);\r\n}\r\n\r\n\r\nfunction getUrlParams()\r\n{\r\n var url = window.location.href;\r\n \r\n var paramString = url.split(\"?\")[1];\r\n if (!paramString) { return {}; }\r\n paramString = paramString.split(\"#\")[0];\r\n var urlParams: { [key: string]: string } = {};\r\n var array = paramString.split(\"&\");\r\n\r\n for (var i = 0; i < array.length; i++) {\r\n var array2 = array[i].split(\"=\");\r\n urlParams[array2[0]] = decodeURIComponent(array2[1]);\r\n }\r\n\r\n return urlParams;\r\n}","import GS from \"../GlobalSetting\";\r\nimport { Viewport } from \"./Viewport\";\r\n\r\n\r\nconst MOBILE_WIDTH = 640;\r\nconst SCALE_WIDTH = 1280;\r\nconst MAX_WIDTH = 1920;\r\n\r\nlet ViewportRoot = new Viewport([MOBILE_WIDTH, SCALE_WIDTH, MAX_WIDTH]);\r\n\r\nexport enum ViewportIndex{\r\n mobile = 0,\r\n pc = 1\r\n}\r\n\r\nexport function getViewportName()\r\n{ \r\n return ViewportRoot.index === 0? \"mobile\": \"pc\";\r\n}\r\n\r\nexport default ViewportRoot;","import { Emitter } from \"strict-event-emitter\";\r\n\r\nexport class Viewport\r\n{\r\n /** 代表目前畫面寬度對應到哪個 ranges 設定(例如 index=0 表示目前畫面寬度介於 0~{ranges[0]} 之間的值) */\r\n index: number = -1;\r\n width: number = 0;\r\n height: number = 0;\r\n changed: boolean = false;\r\n isMajorChange: boolean = false;\r\n ranges: number[];\r\n // scrollbarWidth: number = 0;\r\n\r\n containerWidth: number = 0;\r\n containerHeight: number = 0;\r\n\r\n boundingRect: DOMRect;\r\n\r\n intervalId: number = -1;\r\n\r\n emitter: Emitter<{updated: [viewport: Viewport<{}>], majorChanged: [viewport: Viewport<{}>]}>;\r\n\r\n constructor(ranges?: number[])\r\n { \r\n this.ranges = ranges === undefined ? [640, 1200, 2000] : ranges;\r\n this.emitter = new Emitter();\r\n }\r\n\r\n update()\r\n {\r\n let oldIndex = this.index;\r\n\r\n this.width = window.innerWidth;\r\n this.height = window.innerHeight;\r\n\r\n for (var i = 0; i < this.ranges.length; i++) {\r\n let viewportWidth = this.ranges[i];\r\n if (this.width <= viewportWidth) {\r\n break;\r\n }\r\n }\r\n\r\n this.index = i;\r\n this.changed = (oldIndex !== this.index);\r\n this.isMajorChange = (this.changed && (oldIndex === 0 || this.index === 0)) || oldIndex === -1;\r\n\r\n // if(this.index === 0)\r\n // {\r\n // this.scrollbarWidth = 0;\r\n // }\r\n // else\r\n // {\r\n // this.scrollbarWidth = window.innerWidth - document.body.clientWidth;\r\n // }\r\n\r\n this.boundingRect = new DOMRect(0, 0, this.width, this.height);\r\n\r\n this.emitter.emit(\"updated\", this);\r\n if(this.isMajorChange) this.emitter.emit(\"majorChanged\", this);\r\n }\r\n\r\n test(v: TTT){\r\n console.log(v);\r\n }\r\n\r\n getScrollbarWidth()\r\n {\r\n return this.index === 0? 0: window.innerWidth - document.body.clientWidth;\r\n }\r\n\r\n setRegularUpdate(booleanOrDuration: Boolean | Number = 1, onSizeChanged?: Function)\r\n {\r\n let self = this;\r\n\r\n if (booleanOrDuration === false) {\r\n window.clearInterval(this.intervalId);\r\n }\r\n else {\r\n if (booleanOrDuration === true) booleanOrDuration = 1;\r\n\r\n let duration: number = booleanOrDuration === true ? 1 : booleanOrDuration as number;\r\n\r\n if (self.needUpdate()) onSizeChanged.call(null);\r\n\r\n this.intervalId = window.setInterval(() =>\r\n {\r\n if (self.needUpdate()) onSizeChanged.call(null);\r\n\r\n }, duration * 1000);\r\n }\r\n }\r\n\r\n rsetZoomLevel(width?: number): void\r\n {\r\n if (width === undefined) width = this.ranges[0];\r\n\r\n let clientWidth = screen.width,\r\n mobileWidth = width,\r\n scale = clientWidth / mobileWidth;\r\n\r\n $('meta[name=viewport]').remove();\r\n $('head').append('');\r\n\r\n $('meta[name=viewport]').remove();\r\n $('head').append('');\r\n }\r\n\r\n needUpdate(): boolean\r\n {\r\n return (this.width !== window.innerWidth || this.height !== window.innerHeight);\r\n }\r\n\r\n toString(): string\r\n {\r\n return \"w: \" + this.width + \", height: \" + this.height;\r\n }\r\n}"],"names":["registerPlugin","self","layoutOn","init","build","setApiPath","setting","apiPath","setApiExtension","apiExt","testEnv","setSimplateDelay","window","EZ","completeLinks","$","addEventListener","update","urlParams","layout","toggleClass","popup","isLayoutOn","preInitPage","initPage","active","emitter","addListener","changed","ext","index","html","attr","setupGtag","gaId","_gtag_id_","on","event","eventName","this","getAttribute","eventLabel","gtag","listenerId","$markers","totalMarkers","length","numTriggered","each","elem","_triggered","result","testDom","topInside","bottomInside","pageTitle","pagePath","removeListener","console","log","manualLoaded","$root","load","onload","img","find","$souces","sourceElem","setAttribute","dataset","src","call","ComponentBase","_componentDic","ShoppingPanel","StickBtnBuy","Components","bindScripts","seekAndBind","targetElem","catchSamples","$doms","componentName","obj","componentClass","removeAttribute","detach","$sample","clone","childElem","manualBind","_component","elemList","toArray","forEach","registComponents","dic","Object","assign","createElem","appendTo","$target","append","from","duration","opacity","seekComponentByName","seekComponent","seekComponentById","id","seekComponentByField","field","seekComponentByClass","className","query","error","relyOthers","$scrollWrapper","$pageContent","_oldScrollTop","_oldScrollLeft","coverColor","isImageOnLoading","PhotoViewer","instance","$content","$image","$btnPrev","preventDefault","changeIndex","currentIndex","$btnNext","showSinglePhoto","dataList","showPhoto","updateDots","Popups","to","$dotWrapper","empty","data","$dot","changeImage","gsap","killTweensOf","set","Image","css","updateSize","onerror","ViewportRoot","isOnStage","maxWidth","width","bleedX","maxHeight","height","bleedY","bound","Geom","caculateContain","ratio","delay","PopupBase","_isInit","_isNavOpen","isInit","resize","scale","_isScrollLocking","_scaleInMiddleMode","_contentScaleRate","scaleInMiddleMode","Emitter","scrollTo","y","offsetY","getNavHeight","vp","minWidth","ranges","newHeight","updateHeight","emit","toggleScrollLock","lockOn","scrollY","scrollX","_sendGtagEvents","_gtagEventName","_gtagEventLabel","_setupQuestions","$question","askIndex","isOpen","$ask","$answer","$answerWrapper","event_label","_isShoppingOpen","$shoppingContent","$shoppingTrigger","_resizeShopping","$pending","_isPending","autoClose_cover","EZAlert","close","alert","title","text","alertJSON","pending","cb","onComplete","autoClose","resolveAtStart","closePending","undefined","jsonObj","JSON","stringify","show","options","complexTitle","Error","theme","closeByCover","closeByX","detailMode","mode","cancelLabel","confirmLabel","noButtons","$text","$title","$complexTitle","innerHTML","isSingleLine","Promise","resolve","off","then","confirm","complexConfirm","outputOptions","popupId","autoClose_popup","autoClose_menu","popupAlign","toMe","_ez_popup_to_","$cover","$dropdownMenus","_popupTl","_isLocking","_currentPopup","_currentMenu","_setBackPopup","_popupDic","key","updateActive","prototype","hasOwnProperty","document","querySelector","closeMenu","isMajorChange","onMajorViewportChange","getInstance","popupName","targetPopup","setBackPopup","progress","ease","oldPopup","tl","timeline","x","add","addLabel","openMenu","position","align","offsetTop","offsetLeft","clearDropdownSetting","rect","getBoundingClientRect","top","completeMenu","left","dx","Math","max","getScrollbarWidth","openMenuAsPopup","$popup","string","setMinWidth","vpWidth","isActive","_ajv","AjvProxy","coerceTypes","addSchema","schema","name","getSchema","validate","schemaJson","jsonData","opts","schemaId","errors","message","dir","e","_testData","_apiResponseAfterfixs","_apiExtension","_apiPath","_method","_simulateDelay","_logSendingData","_fetchedPageData","ApiProxy","getCommonPageData","getPageData","getPageDataString","getElementById","jsonString","replace","m","g","validatePageData","parse","logPageData","quickCall","apiName","params","autoClosePending","op","callApi","catch","quickCall2","reject","testDataName","simDelay","method","async","completeWithError","sendSchema","properties","send","definitions","validatedResult","applyTestSendData","response","complete","setTimeout","useApiSchemaData","examples","exampleIndex","sendApi","afterFix","s","getAfterfix","sendingObj","url","crossDomain","type","dataType","contentType","ajax","done","fail","responseSchema","applyTestData","testData","getApiPath","path","setMethod","getMethod","setToken","token","setLogSendingData","b","resetTestData","reset","sendData","applySendData","setApiResponseAfterfix","StyleModes","Direction","_localSetting","logSendingData","_testSetting","ezpointToCash","value","floor","GS","ezpointRatio","pointToCash","priceToEzpoint","round","priceToPoint","getSportsVoucherBonus","amount","ceil","SPORTS_VOUCHER_BONUS_RATE","location","host","extend","i","v","navHeight","default","reviewMaxRating","search","keywordMaxLength","pageSize","getQueryUrl","queryString","search_page_path","DATE_FORMAT","DEFAULT_COUNTRY","_customStyleElem","Tools","createElement","isNumeric","str","isNaN","parseFloat","stringReplaceBetween","start","replaceString","substring","combineName","firstName","surname","match","createLazyLoadImage","formatDateString","dateString","format","moment","getAvatarName","r","$container","$elems","target","$elem","seekComponentElem","removeBetweenArrays","targetList","sourceList","source","splice","formatNumber","num","parseInt","toLocaleString","testRectOverlap","rectA","rectB","right","bottom","testRectOverlapY","addCustomStyle","styles","isMobile","addCustomStyle_2","isReplace","newHtml","scrollToElement","elemOrId","offset","mobile","pc","listToDic","list","splitText","dom","innerText","char","charDom","array","slice","charCodeAt","appendChild","push","parseBoolean","parseDate","addSpace","date","parseValue","valueType","updateValue","valueName","String","getFullName","test","getLocalStorageObj","objString","localStorage","getItem","saveLocalStorageObj","setItem","isLocalStorageAvailable","removeItem","shake","repeat","yoyo","clearProps","setInputFilter","textbox","inputFilter","errMsg","oldValue","oldSelectionStart","selectionStart","oldSelectionEnd","selectionEnd","setSelectionRange","containerWidth","containerHeight","contentWidth","contentHeight","contentRatio","caculateCover","tw","th","contentCenterBound","_listenerDic","_scrollBound","_tweenDic","scrollTop","scrollLeft","_isActive","func","onScroll","updateScrollTop","disactive","removeEventListener","topOffset","bottomOffset","boundTop","boundBottom","testDomFromMiddle","boundMiddle","bothInside","testDomContainScreenMiddle","screenMiddleY","targetTop","__speed","speed","dy","abs","min","onStart","onUpdate","specId","getPosition","evt","forceExecute","doc","documentElement","pageXOffset","clientLeft","pageYOffset","clientTop","innerHeight","getMobileOperatingSystem","clientHeight","ScrollListener","_mobileSystem","Utility","detectIE","ua","navigator","userAgent","msie","indexOf","rv","edge","isUrl","Boolean","getPathWithFilename","href","hash","getPath","substr","split","pop","join","charAt","getProtocol","getUrlParams","getCurrentUrlParams","replaceUrlParams","newQuery","keepState","currentUrl","newUrl","history","replaceState","pushState","adjustUrlParams","removingList","addingDic","k","flag","isLocalhost","address","toString","isiOS","includes","platform","getRealStringLength","vendor","MSStream","orientation","isWebview","u","weixinMatch","toLowerCase","isLineApp","isFbApp","openWindowInCenter","w","h","dualScreenLeft","screenLeft","screenX","dualScreenTop","screenTop","screenY","innerWidth","clientWidth","screen","systemZoom","availWidth","newWindow","open","focus","copyTextToClipboard","clipboard","writeText","err","textArea","style","body","select","successful","execCommand","removeChild","fallbackCopyTextToClipboard","paramString","array2","decodeURIComponent","ViewportIndex","intervalId","oldIndex","viewportWidth","boundingRect","DOMRect","setRegularUpdate","booleanOrDuration","onSizeChanged","clearInterval","needUpdate","setInterval","rsetZoomLevel","remove","getViewportName"],"sourceRoot":""}